diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2009-02-10 15:25:55 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-02-13 13:45:49 -0500 |
commit | 2a5193119269062608582418deba7af82844159a (patch) | |
tree | 1f2fe8cffbeb7530dce7fa708310f6fb29ab0dd8 /net | |
parent | 849b7967818995a32c3017542e33eb3155944368 (diff) |
cfg80211/nl80211: scanning (and mac80211 update to use it)
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>
Diffstat (limited to 'net')
-rw-r--r-- | net/mac80211/cfg.c | 20 | ||||
-rw-r--r-- | net/mac80211/ieee80211_i.h | 18 | ||||
-rw-r--r-- | net/mac80211/iface.c | 2 | ||||
-rw-r--r-- | net/mac80211/main.c | 32 | ||||
-rw-r--r-- | net/mac80211/mlme.c | 37 | ||||
-rw-r--r-- | net/mac80211/scan.c | 356 | ||||
-rw-r--r-- | net/mac80211/wext.c | 59 | ||||
-rw-r--r-- | net/wireless/Makefile | 2 | ||||
-rw-r--r-- | net/wireless/core.c | 8 | ||||
-rw-r--r-- | net/wireless/core.h | 20 | ||||
-rw-r--r-- | net/wireless/nl80211.c | 323 | ||||
-rw-r--r-- | net/wireless/nl80211.h | 8 | ||||
-rw-r--r-- | net/wireless/scan.c | 807 |
13 files changed, 1318 insertions, 374 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 42d692fd9bec..c8d969be440b 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c | |||
@@ -1277,6 +1277,25 @@ static int ieee80211_resume(struct wiphy *wiphy) | |||
1277 | #define ieee80211_resume NULL | 1277 | #define ieee80211_resume NULL |
1278 | #endif | 1278 | #endif |
1279 | 1279 | ||
1280 | static int ieee80211_scan(struct wiphy *wiphy, | ||
1281 | struct net_device *dev, | ||
1282 | struct cfg80211_scan_request *req) | ||
1283 | { | ||
1284 | struct ieee80211_sub_if_data *sdata; | ||
1285 | |||
1286 | if (!netif_running(dev)) | ||
1287 | return -ENETDOWN; | ||
1288 | |||
1289 | sdata = IEEE80211_DEV_TO_SUB_IF(dev); | ||
1290 | |||
1291 | if (sdata->vif.type != NL80211_IFTYPE_STATION && | ||
1292 | sdata->vif.type != NL80211_IFTYPE_ADHOC && | ||
1293 | sdata->vif.type != NL80211_IFTYPE_MESH_POINT) | ||
1294 | return -EOPNOTSUPP; | ||
1295 | |||
1296 | return ieee80211_request_scan(sdata, req); | ||
1297 | } | ||
1298 | |||
1280 | struct cfg80211_ops mac80211_config_ops = { | 1299 | struct cfg80211_ops mac80211_config_ops = { |
1281 | .add_virtual_intf = ieee80211_add_iface, | 1300 | .add_virtual_intf = ieee80211_add_iface, |
1282 | .del_virtual_intf = ieee80211_del_iface, | 1301 | .del_virtual_intf = ieee80211_del_iface, |
@@ -1309,4 +1328,5 @@ struct cfg80211_ops mac80211_config_ops = { | |||
1309 | .set_mgmt_extra_ie = ieee80211_set_mgmt_extra_ie, | 1328 | .set_mgmt_extra_ie = ieee80211_set_mgmt_extra_ie, |
1310 | .suspend = ieee80211_suspend, | 1329 | .suspend = ieee80211_suspend, |
1311 | .resume = ieee80211_resume, | 1330 | .resume = ieee80211_resume, |
1331 | .scan = ieee80211_scan, | ||
1312 | }; | 1332 | }; |
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 9122416fd6af..cbc0b7d647f9 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -294,8 +294,6 @@ struct ieee80211_if_sta { | |||
294 | u8 ssid[IEEE80211_MAX_SSID_LEN]; | 294 | u8 ssid[IEEE80211_MAX_SSID_LEN]; |
295 | enum ieee80211_sta_mlme_state state; | 295 | enum ieee80211_sta_mlme_state state; |
296 | size_t ssid_len; | 296 | size_t ssid_len; |
297 | u8 scan_ssid[IEEE80211_MAX_SSID_LEN]; | ||
298 | size_t scan_ssid_len; | ||
299 | u16 aid; | 297 | u16 aid; |
300 | u16 ap_capab, capab; | 298 | u16 ap_capab, capab; |
301 | u8 *extra_ie; /* to be added to the end of AssocReq */ | 299 | u8 *extra_ie; /* to be added to the end of AssocReq */ |
@@ -658,17 +656,18 @@ struct ieee80211_local { | |||
658 | 656 | ||
659 | /* Scanning and BSS list */ | 657 | /* Scanning and BSS list */ |
660 | bool sw_scanning, hw_scanning; | 658 | bool sw_scanning, hw_scanning; |
659 | struct cfg80211_ssid scan_ssid; | ||
660 | struct cfg80211_scan_request int_scan_req; | ||
661 | struct cfg80211_scan_request *scan_req; | ||
662 | struct ieee80211_channel *scan_channel; | ||
661 | int scan_channel_idx; | 663 | int scan_channel_idx; |
662 | enum ieee80211_band scan_band; | ||
663 | 664 | ||
664 | enum { SCAN_SET_CHANNEL, SCAN_SEND_PROBE } scan_state; | 665 | enum { SCAN_SET_CHANNEL, SCAN_SEND_PROBE } scan_state; |
665 | unsigned long last_scan_completed; | 666 | unsigned long last_scan_completed; |
666 | struct delayed_work scan_work; | 667 | struct delayed_work scan_work; |
667 | struct ieee80211_sub_if_data *scan_sdata; | 668 | struct ieee80211_sub_if_data *scan_sdata; |
668 | struct ieee80211_channel *oper_channel, *scan_channel, *csa_channel; | ||
669 | enum nl80211_channel_type oper_channel_type; | 669 | enum nl80211_channel_type oper_channel_type; |
670 | u8 scan_ssid[IEEE80211_MAX_SSID_LEN]; | 670 | struct ieee80211_channel *oper_channel, *csa_channel; |
671 | size_t scan_ssid_len; | ||
672 | struct list_head bss_list; | 671 | struct list_head bss_list; |
673 | struct ieee80211_bss *bss_hash[STA_HASH_SIZE]; | 672 | struct ieee80211_bss *bss_hash[STA_HASH_SIZE]; |
674 | spinlock_t bss_lock; | 673 | spinlock_t bss_lock; |
@@ -929,7 +928,7 @@ void ieee80211_send_pspoll(struct ieee80211_local *local, | |||
929 | 928 | ||
930 | /* scan/BSS handling */ | 929 | /* scan/BSS handling */ |
931 | int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, | 930 | int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, |
932 | u8 *ssid, size_t ssid_len); | 931 | struct cfg80211_scan_request *req); |
933 | int ieee80211_scan_results(struct ieee80211_local *local, | 932 | int ieee80211_scan_results(struct ieee80211_local *local, |
934 | struct iw_request_info *info, | 933 | struct iw_request_info *info, |
935 | char *buf, size_t len); | 934 | char *buf, size_t len); |
@@ -944,14 +943,15 @@ int ieee80211_sta_set_extra_ie(struct ieee80211_sub_if_data *sdata, | |||
944 | 943 | ||
945 | void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local); | 944 | void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local); |
946 | int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, | 945 | int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, |
947 | u8 *ssid, size_t ssid_len); | 946 | struct cfg80211_scan_request *req); |
948 | struct ieee80211_bss * | 947 | struct ieee80211_bss * |
949 | ieee80211_bss_info_update(struct ieee80211_local *local, | 948 | ieee80211_bss_info_update(struct ieee80211_local *local, |
950 | struct ieee80211_rx_status *rx_status, | 949 | struct ieee80211_rx_status *rx_status, |
951 | struct ieee80211_mgmt *mgmt, | 950 | struct ieee80211_mgmt *mgmt, |
952 | size_t len, | 951 | size_t len, |
953 | struct ieee802_11_elems *elems, | 952 | struct ieee802_11_elems *elems, |
954 | int freq, bool beacon); | 953 | struct ieee80211_channel *channel, |
954 | bool beacon); | ||
955 | struct ieee80211_bss * | 955 | struct ieee80211_bss * |
956 | ieee80211_rx_bss_add(struct ieee80211_local *local, u8 *bssid, int freq, | 956 | ieee80211_rx_bss_add(struct ieee80211_local *local, u8 *bssid, int freq, |
957 | u8 *ssid, u8 ssid_len); | 957 | u8 *ssid, u8 ssid_len); |
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 1c17fb8e4058..df94b9365264 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c | |||
@@ -522,7 +522,7 @@ static int ieee80211_stop(struct net_device *dev) | |||
522 | * scan event to userspace -- the scan is incomplete. | 522 | * scan event to userspace -- the scan is incomplete. |
523 | */ | 523 | */ |
524 | if (local->sw_scanning) | 524 | if (local->sw_scanning) |
525 | ieee80211_scan_completed(&local->hw); | 525 | ieee80211_scan_completed(&local->hw, true); |
526 | } | 526 | } |
527 | 527 | ||
528 | conf.vif = &sdata->vif; | 528 | conf.vif = &sdata->vif; |
diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 956afea4214d..954edfbb6b6f 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c | |||
@@ -733,6 +733,7 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, | |||
733 | return NULL; | 733 | return NULL; |
734 | 734 | ||
735 | wiphy->privid = mac80211_wiphy_privid; | 735 | wiphy->privid = mac80211_wiphy_privid; |
736 | wiphy->max_scan_ssids = 4; | ||
736 | 737 | ||
737 | local = wiphy_priv(wiphy); | 738 | local = wiphy_priv(wiphy); |
738 | local->hw.wiphy = wiphy; | 739 | local->hw.wiphy = wiphy; |
@@ -817,25 +818,33 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) | |||
817 | enum ieee80211_band band; | 818 | enum ieee80211_band band; |
818 | struct net_device *mdev; | 819 | struct net_device *mdev; |
819 | struct ieee80211_master_priv *mpriv; | 820 | struct ieee80211_master_priv *mpriv; |
821 | int channels, i, j; | ||
820 | 822 | ||
821 | /* | 823 | /* |
822 | * generic code guarantees at least one band, | 824 | * generic code guarantees at least one band, |
823 | * set this very early because much code assumes | 825 | * set this very early because much code assumes |
824 | * that hw.conf.channel is assigned | 826 | * that hw.conf.channel is assigned |
825 | */ | 827 | */ |
828 | channels = 0; | ||
826 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | 829 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) { |
827 | struct ieee80211_supported_band *sband; | 830 | struct ieee80211_supported_band *sband; |
828 | 831 | ||
829 | sband = local->hw.wiphy->bands[band]; | 832 | sband = local->hw.wiphy->bands[band]; |
830 | if (sband) { | 833 | if (sband && !local->oper_channel) { |
831 | /* init channel we're on */ | 834 | /* init channel we're on */ |
832 | local->hw.conf.channel = | 835 | local->hw.conf.channel = |
833 | local->oper_channel = | 836 | local->oper_channel = |
834 | local->scan_channel = &sband->channels[0]; | 837 | local->scan_channel = &sband->channels[0]; |
835 | break; | ||
836 | } | 838 | } |
839 | if (sband) | ||
840 | channels += sband->n_channels; | ||
837 | } | 841 | } |
838 | 842 | ||
843 | local->int_scan_req.n_channels = channels; | ||
844 | local->int_scan_req.channels = kzalloc(sizeof(void *) * channels, GFP_KERNEL); | ||
845 | if (!local->int_scan_req.channels) | ||
846 | return -ENOMEM; | ||
847 | |||
839 | /* if low-level driver supports AP, we also support VLAN */ | 848 | /* if low-level driver supports AP, we also support VLAN */ |
840 | if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) | 849 | if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) |
841 | local->hw.wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN); | 850 | local->hw.wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN); |
@@ -845,7 +854,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) | |||
845 | 854 | ||
846 | result = wiphy_register(local->hw.wiphy); | 855 | result = wiphy_register(local->hw.wiphy); |
847 | if (result < 0) | 856 | if (result < 0) |
848 | return result; | 857 | goto fail_wiphy_register; |
849 | 858 | ||
850 | /* | 859 | /* |
851 | * We use the number of queues for feature tests (QoS, HT) internally | 860 | * We use the number of queues for feature tests (QoS, HT) internally |
@@ -948,6 +957,20 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) | |||
948 | 957 | ||
949 | ieee80211_led_init(local); | 958 | ieee80211_led_init(local); |
950 | 959 | ||
960 | /* alloc internal scan request */ | ||
961 | i = 0; | ||
962 | local->int_scan_req.ssids = &local->scan_ssid; | ||
963 | local->int_scan_req.n_ssids = 1; | ||
964 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | ||
965 | if (!hw->wiphy->bands[band]) | ||
966 | continue; | ||
967 | for (j = 0; j < hw->wiphy->bands[band]->n_channels; j++) { | ||
968 | local->int_scan_req.channels[i] = | ||
969 | &hw->wiphy->bands[band]->channels[j]; | ||
970 | i++; | ||
971 | } | ||
972 | } | ||
973 | |||
951 | return 0; | 974 | return 0; |
952 | 975 | ||
953 | fail_wep: | 976 | fail_wep: |
@@ -966,6 +989,8 @@ fail_workqueue: | |||
966 | free_netdev(local->mdev); | 989 | free_netdev(local->mdev); |
967 | fail_mdev_alloc: | 990 | fail_mdev_alloc: |
968 | wiphy_unregister(local->hw.wiphy); | 991 | wiphy_unregister(local->hw.wiphy); |
992 | fail_wiphy_register: | ||
993 | kfree(local->int_scan_req.channels); | ||
969 | return result; | 994 | return result; |
970 | } | 995 | } |
971 | EXPORT_SYMBOL(ieee80211_register_hw); | 996 | EXPORT_SYMBOL(ieee80211_register_hw); |
@@ -1011,6 +1036,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw) | |||
1011 | ieee80211_wep_free(local); | 1036 | ieee80211_wep_free(local); |
1012 | ieee80211_led_exit(local); | 1037 | ieee80211_led_exit(local); |
1013 | free_netdev(local->mdev); | 1038 | free_netdev(local->mdev); |
1039 | kfree(local->int_scan_req.channels); | ||
1014 | } | 1040 | } |
1015 | EXPORT_SYMBOL(ieee80211_unregister_hw); | 1041 | EXPORT_SYMBOL(ieee80211_unregister_hw); |
1016 | 1042 | ||
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index bfc47b330687..46b4817cdea9 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c | |||
@@ -1743,7 +1743,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, | |||
1743 | } | 1743 | } |
1744 | 1744 | ||
1745 | bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, elems, | 1745 | bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, elems, |
1746 | freq, beacon); | 1746 | channel, beacon); |
1747 | if (!bss) | 1747 | if (!bss) |
1748 | return; | 1748 | return; |
1749 | 1749 | ||
@@ -2162,7 +2162,15 @@ static void ieee80211_sta_merge_ibss(struct ieee80211_sub_if_data *sdata, | |||
2162 | 2162 | ||
2163 | printk(KERN_DEBUG "%s: No active IBSS STAs - trying to scan for other " | 2163 | printk(KERN_DEBUG "%s: No active IBSS STAs - trying to scan for other " |
2164 | "IBSS networks with same SSID (merge)\n", sdata->dev->name); | 2164 | "IBSS networks with same SSID (merge)\n", sdata->dev->name); |
2165 | ieee80211_request_scan(sdata, ifsta->ssid, ifsta->ssid_len); | 2165 | |
2166 | /* XXX maybe racy? */ | ||
2167 | if (sdata->local->scan_req) | ||
2168 | return; | ||
2169 | |||
2170 | memcpy(sdata->local->int_scan_req.ssids[0].ssid, | ||
2171 | ifsta->ssid, IEEE80211_MAX_SSID_LEN); | ||
2172 | sdata->local->int_scan_req.ssids[0].ssid_len = ifsta->ssid_len; | ||
2173 | ieee80211_request_scan(sdata, &sdata->local->int_scan_req); | ||
2166 | } | 2174 | } |
2167 | 2175 | ||
2168 | 2176 | ||
@@ -2378,8 +2386,15 @@ dont_join: | |||
2378 | IEEE80211_SCAN_INTERVAL)) { | 2386 | IEEE80211_SCAN_INTERVAL)) { |
2379 | printk(KERN_DEBUG "%s: Trigger new scan to find an IBSS to " | 2387 | printk(KERN_DEBUG "%s: Trigger new scan to find an IBSS to " |
2380 | "join\n", sdata->dev->name); | 2388 | "join\n", sdata->dev->name); |
2381 | return ieee80211_request_scan(sdata, ifsta->ssid, | 2389 | |
2382 | ifsta->ssid_len); | 2390 | /* XXX maybe racy? */ |
2391 | if (local->scan_req) | ||
2392 | return -EBUSY; | ||
2393 | |||
2394 | memcpy(local->int_scan_req.ssids[0].ssid, | ||
2395 | ifsta->ssid, IEEE80211_MAX_SSID_LEN); | ||
2396 | local->int_scan_req.ssids[0].ssid_len = ifsta->ssid_len; | ||
2397 | return ieee80211_request_scan(sdata, &local->int_scan_req); | ||
2383 | } else if (ifsta->state != IEEE80211_STA_MLME_IBSS_JOINED) { | 2398 | } else if (ifsta->state != IEEE80211_STA_MLME_IBSS_JOINED) { |
2384 | int interval = IEEE80211_SCAN_INTERVAL; | 2399 | int interval = IEEE80211_SCAN_INTERVAL; |
2385 | 2400 | ||
@@ -2478,11 +2493,16 @@ static int ieee80211_sta_config_auth(struct ieee80211_sub_if_data *sdata, | |||
2478 | } else { | 2493 | } else { |
2479 | if (ifsta->assoc_scan_tries < IEEE80211_ASSOC_SCANS_MAX_TRIES) { | 2494 | if (ifsta->assoc_scan_tries < IEEE80211_ASSOC_SCANS_MAX_TRIES) { |
2480 | ifsta->assoc_scan_tries++; | 2495 | ifsta->assoc_scan_tries++; |
2496 | /* XXX maybe racy? */ | ||
2497 | if (local->scan_req) | ||
2498 | return -1; | ||
2499 | memcpy(local->int_scan_req.ssids[0].ssid, | ||
2500 | ifsta->ssid, IEEE80211_MAX_SSID_LEN); | ||
2481 | if (ifsta->flags & IEEE80211_STA_AUTO_SSID_SEL) | 2501 | if (ifsta->flags & IEEE80211_STA_AUTO_SSID_SEL) |
2482 | ieee80211_start_scan(sdata, NULL, 0); | 2502 | local->int_scan_req.ssids[0].ssid_len = 0; |
2483 | else | 2503 | else |
2484 | ieee80211_start_scan(sdata, ifsta->ssid, | 2504 | local->int_scan_req.ssids[0].ssid_len = ifsta->ssid_len; |
2485 | ifsta->ssid_len); | 2505 | ieee80211_start_scan(sdata, &local->int_scan_req); |
2486 | ifsta->state = IEEE80211_STA_MLME_AUTHENTICATE; | 2506 | ifsta->state = IEEE80211_STA_MLME_AUTHENTICATE; |
2487 | set_bit(IEEE80211_STA_REQ_AUTH, &ifsta->request); | 2507 | set_bit(IEEE80211_STA_REQ_AUTH, &ifsta->request); |
2488 | } else { | 2508 | } else { |
@@ -2520,8 +2540,7 @@ static void ieee80211_sta_work(struct work_struct *work) | |||
2520 | ifsta->state != IEEE80211_STA_MLME_AUTHENTICATE && | 2540 | ifsta->state != IEEE80211_STA_MLME_AUTHENTICATE && |
2521 | ifsta->state != IEEE80211_STA_MLME_ASSOCIATE && | 2541 | ifsta->state != IEEE80211_STA_MLME_ASSOCIATE && |
2522 | test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request)) { | 2542 | test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request)) { |
2523 | ieee80211_start_scan(sdata, ifsta->scan_ssid, | 2543 | ieee80211_start_scan(sdata, local->scan_req); |
2524 | ifsta->scan_ssid_len); | ||
2525 | return; | 2544 | return; |
2526 | } | 2545 | } |
2527 | 2546 | ||
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index eddca4e1e13c..c6b275b10cf9 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c | |||
@@ -13,6 +13,9 @@ | |||
13 | */ | 13 | */ |
14 | 14 | ||
15 | /* TODO: | 15 | /* TODO: |
16 | * figure out how to avoid that the "current BSS" expires | ||
17 | * clean up IBSS code (in MLME), see why it adds a BSS to the list | ||
18 | * use cfg80211's BSS handling (depends on IBSS TODO above) | ||
16 | * order BSS list by RSSI(?) ("quality of AP") | 19 | * order BSS list by RSSI(?) ("quality of AP") |
17 | * scan result table filtering (by capability (privacy, IBSS/BSS, WPA/RSN IE, | 20 | * scan result table filtering (by capability (privacy, IBSS/BSS, WPA/RSN IE, |
18 | * SSID) | 21 | * SSID) |
@@ -225,10 +228,26 @@ ieee80211_bss_info_update(struct ieee80211_local *local, | |||
225 | struct ieee80211_mgmt *mgmt, | 228 | struct ieee80211_mgmt *mgmt, |
226 | size_t len, | 229 | size_t len, |
227 | struct ieee802_11_elems *elems, | 230 | struct ieee802_11_elems *elems, |
228 | int freq, bool beacon) | 231 | struct ieee80211_channel *channel, |
232 | bool beacon) | ||
229 | { | 233 | { |
230 | struct ieee80211_bss *bss; | 234 | struct ieee80211_bss *bss; |
231 | int clen; | 235 | int clen, freq = channel->center_freq; |
236 | enum cfg80211_signal_type sigtype = CFG80211_SIGNAL_TYPE_NONE; | ||
237 | s32 signal = 0; | ||
238 | |||
239 | if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) { | ||
240 | sigtype = CFG80211_SIGNAL_TYPE_MBM; | ||
241 | signal = rx_status->signal * 100; | ||
242 | } else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) { | ||
243 | sigtype = CFG80211_SIGNAL_TYPE_UNSPEC; | ||
244 | signal = (rx_status->signal * 100) / local->hw.max_signal; | ||
245 | } | ||
246 | |||
247 | cfg80211_put_bss( | ||
248 | cfg80211_inform_bss_frame(local->hw.wiphy, channel, | ||
249 | mgmt, len, signal, sigtype, | ||
250 | GFP_ATOMIC)); | ||
232 | 251 | ||
233 | #ifdef CONFIG_MAC80211_MESH | 252 | #ifdef CONFIG_MAC80211_MESH |
234 | if (elems->mesh_config) | 253 | if (elems->mesh_config) |
@@ -401,7 +420,7 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, | |||
401 | 420 | ||
402 | bss = ieee80211_bss_info_update(sdata->local, rx_status, | 421 | bss = ieee80211_bss_info_update(sdata->local, rx_status, |
403 | mgmt, skb->len, &elems, | 422 | mgmt, skb->len, &elems, |
404 | freq, beacon); | 423 | channel, beacon); |
405 | if (bss) | 424 | if (bss) |
406 | ieee80211_rx_bss_put(sdata->local, bss); | 425 | ieee80211_rx_bss_put(sdata->local, bss); |
407 | 426 | ||
@@ -439,26 +458,22 @@ void ieee80211_send_nullfunc(struct ieee80211_local *local, | |||
439 | ieee80211_tx_skb(sdata, skb, 0); | 458 | ieee80211_tx_skb(sdata, skb, 0); |
440 | } | 459 | } |
441 | 460 | ||
442 | void ieee80211_scan_completed(struct ieee80211_hw *hw) | 461 | void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) |
443 | { | 462 | { |
444 | struct ieee80211_local *local = hw_to_local(hw); | 463 | struct ieee80211_local *local = hw_to_local(hw); |
445 | struct ieee80211_sub_if_data *sdata; | 464 | struct ieee80211_sub_if_data *sdata; |
446 | union iwreq_data wrqu; | ||
447 | 465 | ||
448 | if (WARN_ON(!local->hw_scanning && !local->sw_scanning)) | 466 | if (WARN_ON(!local->hw_scanning && !local->sw_scanning)) |
449 | return; | 467 | return; |
450 | 468 | ||
451 | local->last_scan_completed = jiffies; | 469 | if (WARN_ON(!local->scan_req)) |
452 | memset(&wrqu, 0, sizeof(wrqu)); | 470 | return; |
453 | 471 | ||
454 | /* | 472 | if (local->scan_req != &local->int_scan_req) |
455 | * local->scan_sdata could have been NULLed by the interface | 473 | cfg80211_scan_done(local->scan_req, aborted); |
456 | * down code in case we were scanning on an interface that is | 474 | local->scan_req = NULL; |
457 | * being taken down. | 475 | |
458 | */ | 476 | local->last_scan_completed = jiffies; |
459 | sdata = local->scan_sdata; | ||
460 | if (sdata) | ||
461 | wireless_send_event(sdata->dev, SIOCGIWSCAN, &wrqu, NULL); | ||
462 | 477 | ||
463 | if (local->hw_scanning) { | 478 | if (local->hw_scanning) { |
464 | local->hw_scanning = false; | 479 | local->hw_scanning = false; |
@@ -520,9 +535,8 @@ void ieee80211_scan_work(struct work_struct *work) | |||
520 | struct ieee80211_local *local = | 535 | struct ieee80211_local *local = |
521 | container_of(work, struct ieee80211_local, scan_work.work); | 536 | container_of(work, struct ieee80211_local, scan_work.work); |
522 | struct ieee80211_sub_if_data *sdata = local->scan_sdata; | 537 | struct ieee80211_sub_if_data *sdata = local->scan_sdata; |
523 | struct ieee80211_supported_band *sband; | ||
524 | struct ieee80211_channel *chan; | 538 | struct ieee80211_channel *chan; |
525 | int skip; | 539 | int skip, i; |
526 | unsigned long next_delay = 0; | 540 | unsigned long next_delay = 0; |
527 | 541 | ||
528 | /* | 542 | /* |
@@ -533,33 +547,13 @@ void ieee80211_scan_work(struct work_struct *work) | |||
533 | 547 | ||
534 | switch (local->scan_state) { | 548 | switch (local->scan_state) { |
535 | case SCAN_SET_CHANNEL: | 549 | case SCAN_SET_CHANNEL: |
536 | /* | ||
537 | * Get current scan band. scan_band may be IEEE80211_NUM_BANDS | ||
538 | * after we successfully scanned the last channel of the last | ||
539 | * band (and the last band is supported by the hw) | ||
540 | */ | ||
541 | if (local->scan_band < IEEE80211_NUM_BANDS) | ||
542 | sband = local->hw.wiphy->bands[local->scan_band]; | ||
543 | else | ||
544 | sband = NULL; | ||
545 | |||
546 | /* | ||
547 | * If we are at an unsupported band and have more bands | ||
548 | * left to scan, advance to the next supported one. | ||
549 | */ | ||
550 | while (!sband && local->scan_band < IEEE80211_NUM_BANDS - 1) { | ||
551 | local->scan_band++; | ||
552 | sband = local->hw.wiphy->bands[local->scan_band]; | ||
553 | local->scan_channel_idx = 0; | ||
554 | } | ||
555 | |||
556 | /* if no more bands/channels left, complete scan */ | 550 | /* if no more bands/channels left, complete scan */ |
557 | if (!sband || local->scan_channel_idx >= sband->n_channels) { | 551 | if (local->scan_channel_idx >= local->scan_req->n_channels) { |
558 | ieee80211_scan_completed(local_to_hw(local)); | 552 | ieee80211_scan_completed(local_to_hw(local), false); |
559 | return; | 553 | return; |
560 | } | 554 | } |
561 | skip = 0; | 555 | skip = 0; |
562 | chan = &sband->channels[local->scan_channel_idx]; | 556 | chan = local->scan_req->channels[local->scan_channel_idx]; |
563 | 557 | ||
564 | if (chan->flags & IEEE80211_CHAN_DISABLED || | 558 | if (chan->flags & IEEE80211_CHAN_DISABLED || |
565 | (sdata->vif.type == NL80211_IFTYPE_ADHOC && | 559 | (sdata->vif.type == NL80211_IFTYPE_ADHOC && |
@@ -575,15 +569,6 @@ void ieee80211_scan_work(struct work_struct *work) | |||
575 | 569 | ||
576 | /* advance state machine to next channel/band */ | 570 | /* advance state machine to next channel/band */ |
577 | local->scan_channel_idx++; | 571 | local->scan_channel_idx++; |
578 | if (local->scan_channel_idx >= sband->n_channels) { | ||
579 | /* | ||
580 | * scan_band may end up == IEEE80211_NUM_BANDS, but | ||
581 | * we'll catch that case above and complete the scan | ||
582 | * if that is the case. | ||
583 | */ | ||
584 | local->scan_band++; | ||
585 | local->scan_channel_idx = 0; | ||
586 | } | ||
587 | 572 | ||
588 | if (skip) | 573 | if (skip) |
589 | break; | 574 | break; |
@@ -596,10 +581,14 @@ void ieee80211_scan_work(struct work_struct *work) | |||
596 | next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; | 581 | next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; |
597 | local->scan_state = SCAN_SET_CHANNEL; | 582 | local->scan_state = SCAN_SET_CHANNEL; |
598 | 583 | ||
599 | if (local->scan_channel->flags & IEEE80211_CHAN_PASSIVE_SCAN) | 584 | if (local->scan_channel->flags & IEEE80211_CHAN_PASSIVE_SCAN || |
585 | !local->scan_req->n_ssids) | ||
600 | break; | 586 | break; |
601 | ieee80211_send_probe_req(sdata, NULL, local->scan_ssid, | 587 | for (i = 0; i < local->scan_req->n_ssids; i++) |
602 | local->scan_ssid_len); | 588 | ieee80211_send_probe_req( |
589 | sdata, NULL, | ||
590 | local->scan_req->ssids[i].ssid, | ||
591 | local->scan_req->ssids[i].ssid_len); | ||
603 | next_delay = IEEE80211_CHANNEL_TIME; | 592 | next_delay = IEEE80211_CHANNEL_TIME; |
604 | break; | 593 | break; |
605 | } | 594 | } |
@@ -610,14 +599,19 @@ void ieee80211_scan_work(struct work_struct *work) | |||
610 | 599 | ||
611 | 600 | ||
612 | int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, | 601 | int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, |
613 | u8 *ssid, size_t ssid_len) | 602 | struct cfg80211_scan_request *req) |
614 | { | 603 | { |
615 | struct ieee80211_local *local = scan_sdata->local; | 604 | struct ieee80211_local *local = scan_sdata->local; |
616 | struct ieee80211_sub_if_data *sdata; | 605 | struct ieee80211_sub_if_data *sdata; |
617 | 606 | ||
618 | if (ssid_len > IEEE80211_MAX_SSID_LEN) | 607 | if (!req) |
619 | return -EINVAL; | 608 | return -EINVAL; |
620 | 609 | ||
610 | if (local->scan_req && local->scan_req != req) | ||
611 | return -EBUSY; | ||
612 | |||
613 | local->scan_req = req; | ||
614 | |||
621 | /* MLME-SCAN.request (page 118) page 144 (11.1.3.1) | 615 | /* MLME-SCAN.request (page 118) page 144 (11.1.3.1) |
622 | * BSSType: INFRASTRUCTURE, INDEPENDENT, ANY_BSS | 616 | * BSSType: INFRASTRUCTURE, INDEPENDENT, ANY_BSS |
623 | * BSSID: MACAddress | 617 | * BSSID: MACAddress |
@@ -645,7 +639,7 @@ int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, | |||
645 | int rc; | 639 | int rc; |
646 | 640 | ||
647 | local->hw_scanning = true; | 641 | local->hw_scanning = true; |
648 | rc = local->ops->hw_scan(local_to_hw(local), ssid, ssid_len); | 642 | rc = local->ops->hw_scan(local_to_hw(local), req); |
649 | if (rc) { | 643 | if (rc) { |
650 | local->hw_scanning = false; | 644 | local->hw_scanning = false; |
651 | return rc; | 645 | return rc; |
@@ -678,15 +672,10 @@ int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, | |||
678 | } | 672 | } |
679 | mutex_unlock(&local->iflist_mtx); | 673 | mutex_unlock(&local->iflist_mtx); |
680 | 674 | ||
681 | if (ssid) { | ||
682 | local->scan_ssid_len = ssid_len; | ||
683 | memcpy(local->scan_ssid, ssid, ssid_len); | ||
684 | } else | ||
685 | local->scan_ssid_len = 0; | ||
686 | local->scan_state = SCAN_SET_CHANNEL; | 675 | local->scan_state = SCAN_SET_CHANNEL; |
687 | local->scan_channel_idx = 0; | 676 | local->scan_channel_idx = 0; |
688 | local->scan_band = IEEE80211_BAND_2GHZ; | ||
689 | local->scan_sdata = scan_sdata; | 677 | local->scan_sdata = scan_sdata; |
678 | local->scan_req = req; | ||
690 | 679 | ||
691 | netif_addr_lock_bh(local->mdev); | 680 | netif_addr_lock_bh(local->mdev); |
692 | local->filter_flags |= FIF_BCN_PRBRESP_PROMISC; | 681 | local->filter_flags |= FIF_BCN_PRBRESP_PROMISC; |
@@ -706,13 +695,21 @@ int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, | |||
706 | 695 | ||
707 | 696 | ||
708 | int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, | 697 | int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, |
709 | u8 *ssid, size_t ssid_len) | 698 | struct cfg80211_scan_request *req) |
710 | { | 699 | { |
711 | struct ieee80211_local *local = sdata->local; | 700 | struct ieee80211_local *local = sdata->local; |
712 | struct ieee80211_if_sta *ifsta; | 701 | struct ieee80211_if_sta *ifsta; |
713 | 702 | ||
703 | if (!req) | ||
704 | return -EINVAL; | ||
705 | |||
706 | if (local->scan_req && local->scan_req != req) | ||
707 | return -EBUSY; | ||
708 | |||
709 | local->scan_req = req; | ||
710 | |||
714 | if (sdata->vif.type != NL80211_IFTYPE_STATION) | 711 | if (sdata->vif.type != NL80211_IFTYPE_STATION) |
715 | return ieee80211_start_scan(sdata, ssid, ssid_len); | 712 | return ieee80211_start_scan(sdata, req); |
716 | 713 | ||
717 | /* | 714 | /* |
718 | * STA has a state machine that might need to defer scanning | 715 | * 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, | |||
727 | } | 724 | } |
728 | 725 | ||
729 | ifsta = &sdata->u.sta; | 726 | ifsta = &sdata->u.sta; |
730 | |||
731 | ifsta->scan_ssid_len = ssid_len; | ||
732 | if (ssid_len) | ||
733 | memcpy(ifsta->scan_ssid, ssid, ssid_len); | ||
734 | set_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request); | 727 | set_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request); |
735 | queue_work(local->hw.workqueue, &ifsta->work); | 728 | queue_work(local->hw.workqueue, &ifsta->work); |
736 | 729 | ||
737 | return 0; | 730 | return 0; |
738 | } | 731 | } |
739 | |||
740 | |||
741 | static void ieee80211_scan_add_ies(struct iw_request_info *info, | ||
742 | struct ieee80211_bss *bss, | ||
743 | char **current_ev, char *end_buf) | ||
744 | { | ||
745 | u8 *pos, *end, *next; | ||
746 | struct iw_event iwe; | ||
747 | |||
748 | if (bss == NULL || bss->ies == NULL) | ||
749 | return; | ||
750 | |||
751 | /* | ||
752 | * If needed, fragment the IEs buffer (at IE boundaries) into short | ||
753 | * enough fragments to fit into IW_GENERIC_IE_MAX octet messages. | ||
754 | */ | ||
755 | pos = bss->ies; | ||
756 | end = pos + bss->ies_len; | ||
757 | |||
758 | while (end - pos > IW_GENERIC_IE_MAX) { | ||
759 | next = pos + 2 + pos[1]; | ||
760 | while (next + 2 + next[1] - pos < IW_GENERIC_IE_MAX) | ||
761 | next = next + 2 + next[1]; | ||
762 | |||
763 | memset(&iwe, 0, sizeof(iwe)); | ||
764 | iwe.cmd = IWEVGENIE; | ||
765 | iwe.u.data.length = next - pos; | ||
766 | *current_ev = iwe_stream_add_point(info, *current_ev, | ||
767 | end_buf, &iwe, pos); | ||
768 | |||
769 | pos = next; | ||
770 | } | ||
771 | |||
772 | if (end > pos) { | ||
773 | memset(&iwe, 0, sizeof(iwe)); | ||
774 | iwe.cmd = IWEVGENIE; | ||
775 | iwe.u.data.length = end - pos; | ||
776 | *current_ev = iwe_stream_add_point(info, *current_ev, | ||
777 | end_buf, &iwe, pos); | ||
778 | } | ||
779 | } | ||
780 | |||
781 | |||
782 | static char * | ||
783 | ieee80211_scan_result(struct ieee80211_local *local, | ||
784 | struct iw_request_info *info, | ||
785 | struct ieee80211_bss *bss, | ||
786 | char *current_ev, char *end_buf) | ||
787 | { | ||
788 | struct iw_event iwe; | ||
789 | char *buf; | ||
790 | |||
791 | if (time_after(jiffies, | ||
792 | bss->last_update + IEEE80211_SCAN_RESULT_EXPIRE)) | ||
793 | return current_ev; | ||
794 | |||
795 | memset(&iwe, 0, sizeof(iwe)); | ||
796 | iwe.cmd = SIOCGIWAP; | ||
797 | iwe.u.ap_addr.sa_family = ARPHRD_ETHER; | ||
798 | memcpy(iwe.u.ap_addr.sa_data, bss->bssid, ETH_ALEN); | ||
799 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
800 | IW_EV_ADDR_LEN); | ||
801 | |||
802 | memset(&iwe, 0, sizeof(iwe)); | ||
803 | iwe.cmd = SIOCGIWESSID; | ||
804 | if (bss_mesh_cfg(bss)) { | ||
805 | iwe.u.data.length = bss_mesh_id_len(bss); | ||
806 | iwe.u.data.flags = 1; | ||
807 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
808 | &iwe, bss_mesh_id(bss)); | ||
809 | } else { | ||
810 | iwe.u.data.length = bss->ssid_len; | ||
811 | iwe.u.data.flags = 1; | ||
812 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
813 | &iwe, bss->ssid); | ||
814 | } | ||
815 | |||
816 | if (bss->capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS) | ||
817 | || bss_mesh_cfg(bss)) { | ||
818 | memset(&iwe, 0, sizeof(iwe)); | ||
819 | iwe.cmd = SIOCGIWMODE; | ||
820 | if (bss_mesh_cfg(bss)) | ||
821 | iwe.u.mode = IW_MODE_MESH; | ||
822 | else if (bss->capability & WLAN_CAPABILITY_ESS) | ||
823 | iwe.u.mode = IW_MODE_MASTER; | ||
824 | else | ||
825 | iwe.u.mode = IW_MODE_ADHOC; | ||
826 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, | ||
827 | &iwe, IW_EV_UINT_LEN); | ||
828 | } | ||
829 | |||
830 | memset(&iwe, 0, sizeof(iwe)); | ||
831 | iwe.cmd = SIOCGIWFREQ; | ||
832 | iwe.u.freq.m = ieee80211_frequency_to_channel(bss->freq); | ||
833 | iwe.u.freq.e = 0; | ||
834 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
835 | IW_EV_FREQ_LEN); | ||
836 | |||
837 | memset(&iwe, 0, sizeof(iwe)); | ||
838 | iwe.cmd = SIOCGIWFREQ; | ||
839 | iwe.u.freq.m = bss->freq; | ||
840 | iwe.u.freq.e = 6; | ||
841 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
842 | IW_EV_FREQ_LEN); | ||
843 | memset(&iwe, 0, sizeof(iwe)); | ||
844 | iwe.cmd = IWEVQUAL; | ||
845 | iwe.u.qual.qual = bss->qual; | ||
846 | iwe.u.qual.level = bss->signal; | ||
847 | iwe.u.qual.noise = bss->noise; | ||
848 | iwe.u.qual.updated = local->wstats_flags; | ||
849 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
850 | IW_EV_QUAL_LEN); | ||
851 | |||
852 | memset(&iwe, 0, sizeof(iwe)); | ||
853 | iwe.cmd = SIOCGIWENCODE; | ||
854 | if (bss->capability & WLAN_CAPABILITY_PRIVACY) | ||
855 | iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; | ||
856 | else | ||
857 | iwe.u.data.flags = IW_ENCODE_DISABLED; | ||
858 | iwe.u.data.length = 0; | ||
859 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
860 | &iwe, ""); | ||
861 | |||
862 | ieee80211_scan_add_ies(info, bss, ¤t_ev, end_buf); | ||
863 | |||
864 | if (bss->supp_rates_len > 0) { | ||
865 | /* display all supported rates in readable format */ | ||
866 | char *p = current_ev + iwe_stream_lcp_len(info); | ||
867 | int i; | ||
868 | |||
869 | memset(&iwe, 0, sizeof(iwe)); | ||
870 | iwe.cmd = SIOCGIWRATE; | ||
871 | /* Those two flags are ignored... */ | ||
872 | iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; | ||
873 | |||
874 | for (i = 0; i < bss->supp_rates_len; i++) { | ||
875 | iwe.u.bitrate.value = ((bss->supp_rates[i] & | ||
876 | 0x7f) * 500000); | ||
877 | p = iwe_stream_add_value(info, current_ev, p, | ||
878 | end_buf, &iwe, IW_EV_PARAM_LEN); | ||
879 | } | ||
880 | current_ev = p; | ||
881 | } | ||
882 | |||
883 | buf = kmalloc(30, GFP_ATOMIC); | ||
884 | if (buf) { | ||
885 | memset(&iwe, 0, sizeof(iwe)); | ||
886 | iwe.cmd = IWEVCUSTOM; | ||
887 | sprintf(buf, "tsf=%016llx", (unsigned long long)(bss->timestamp)); | ||
888 | iwe.u.data.length = strlen(buf); | ||
889 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
890 | &iwe, buf); | ||
891 | memset(&iwe, 0, sizeof(iwe)); | ||
892 | iwe.cmd = IWEVCUSTOM; | ||
893 | sprintf(buf, " Last beacon: %dms ago", | ||
894 | jiffies_to_msecs(jiffies - bss->last_update)); | ||
895 | iwe.u.data.length = strlen(buf); | ||
896 | current_ev = iwe_stream_add_point(info, current_ev, | ||
897 | end_buf, &iwe, buf); | ||
898 | kfree(buf); | ||
899 | } | ||
900 | |||
901 | if (bss_mesh_cfg(bss)) { | ||
902 | u8 *cfg = bss_mesh_cfg(bss); | ||
903 | buf = kmalloc(50, GFP_ATOMIC); | ||
904 | if (buf) { | ||
905 | memset(&iwe, 0, sizeof(iwe)); | ||
906 | iwe.cmd = IWEVCUSTOM; | ||
907 | sprintf(buf, "Mesh network (version %d)", cfg[0]); | ||
908 | iwe.u.data.length = strlen(buf); | ||
909 | current_ev = iwe_stream_add_point(info, current_ev, | ||
910 | end_buf, | ||
911 | &iwe, buf); | ||
912 | sprintf(buf, "Path Selection Protocol ID: " | ||
913 | "0x%02X%02X%02X%02X", cfg[1], cfg[2], cfg[3], | ||
914 | cfg[4]); | ||
915 | iwe.u.data.length = strlen(buf); | ||
916 | current_ev = iwe_stream_add_point(info, current_ev, | ||
917 | end_buf, | ||
918 | &iwe, buf); | ||
919 | sprintf(buf, "Path Selection Metric ID: " | ||
920 | "0x%02X%02X%02X%02X", cfg[5], cfg[6], cfg[7], | ||
921 | cfg[8]); | ||
922 | iwe.u.data.length = strlen(buf); | ||
923 | current_ev = iwe_stream_add_point(info, current_ev, | ||
924 | end_buf, | ||
925 | &iwe, buf); | ||
926 | sprintf(buf, "Congestion Control Mode ID: " | ||
927 | "0x%02X%02X%02X%02X", cfg[9], cfg[10], | ||
928 | cfg[11], cfg[12]); | ||
929 | iwe.u.data.length = strlen(buf); | ||
930 | current_ev = iwe_stream_add_point(info, current_ev, | ||
931 | end_buf, | ||
932 | &iwe, buf); | ||
933 | sprintf(buf, "Channel Precedence: " | ||
934 | "0x%02X%02X%02X%02X", cfg[13], cfg[14], | ||
935 | cfg[15], cfg[16]); | ||
936 | iwe.u.data.length = strlen(buf); | ||
937 | current_ev = iwe_stream_add_point(info, current_ev, | ||
938 | end_buf, | ||
939 | &iwe, buf); | ||
940 | kfree(buf); | ||
941 | } | ||
942 | } | ||
943 | |||
944 | return current_ev; | ||
945 | } | ||
946 | |||
947 | |||
948 | int ieee80211_scan_results(struct ieee80211_local *local, | ||
949 | struct iw_request_info *info, | ||
950 | char *buf, size_t len) | ||
951 | { | ||
952 | char *current_ev = buf; | ||
953 | char *end_buf = buf + len; | ||
954 | struct ieee80211_bss *bss; | ||
955 | |||
956 | spin_lock_bh(&local->bss_lock); | ||
957 | list_for_each_entry(bss, &local->bss_list, list) { | ||
958 | if (buf + len - current_ev <= IW_EV_ADDR_LEN) { | ||
959 | spin_unlock_bh(&local->bss_lock); | ||
960 | return -E2BIG; | ||
961 | } | ||
962 | current_ev = ieee80211_scan_result(local, info, bss, | ||
963 | current_ev, end_buf); | ||
964 | } | ||
965 | spin_unlock_bh(&local->bss_lock); | ||
966 | return current_ev - buf; | ||
967 | } | ||
diff --git a/net/mac80211/wext.c b/net/mac80211/wext.c index acd5808b87f4..b337d7d5edb3 100644 --- a/net/mac80211/wext.c +++ b/net/mac80211/wext.c | |||
@@ -173,8 +173,9 @@ static int ieee80211_ioctl_giwrange(struct net_device *dev, | |||
173 | range->num_encoding_sizes = 2; | 173 | range->num_encoding_sizes = 2; |
174 | range->max_encoding_tokens = NUM_DEFAULT_KEYS; | 174 | range->max_encoding_tokens = NUM_DEFAULT_KEYS; |
175 | 175 | ||
176 | /* cfg80211 requires this, and enforces 0..100 */ | ||
176 | if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) | 177 | if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) |
177 | range->max_qual.level = local->hw.max_signal; | 178 | range->max_qual.level = 100; |
178 | else if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) | 179 | else if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) |
179 | range->max_qual.level = -110; | 180 | range->max_qual.level = -110; |
180 | else | 181 | else |
@@ -415,58 +416,6 @@ static int ieee80211_ioctl_giwap(struct net_device *dev, | |||
415 | } | 416 | } |
416 | 417 | ||
417 | 418 | ||
418 | static int ieee80211_ioctl_siwscan(struct net_device *dev, | ||
419 | struct iw_request_info *info, | ||
420 | union iwreq_data *wrqu, char *extra) | ||
421 | { | ||
422 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | ||
423 | struct iw_scan_req *req = NULL; | ||
424 | u8 *ssid = NULL; | ||
425 | size_t ssid_len = 0; | ||
426 | |||
427 | if (!netif_running(dev)) | ||
428 | return -ENETDOWN; | ||
429 | |||
430 | if (sdata->vif.type != NL80211_IFTYPE_STATION && | ||
431 | sdata->vif.type != NL80211_IFTYPE_ADHOC && | ||
432 | sdata->vif.type != NL80211_IFTYPE_MESH_POINT) | ||
433 | return -EOPNOTSUPP; | ||
434 | |||
435 | /* if SSID was specified explicitly then use that */ | ||
436 | if (wrqu->data.length == sizeof(struct iw_scan_req) && | ||
437 | wrqu->data.flags & IW_SCAN_THIS_ESSID) { | ||
438 | req = (struct iw_scan_req *)extra; | ||
439 | ssid = req->essid; | ||
440 | ssid_len = req->essid_len; | ||
441 | } | ||
442 | |||
443 | return ieee80211_request_scan(sdata, ssid, ssid_len); | ||
444 | } | ||
445 | |||
446 | |||
447 | static int ieee80211_ioctl_giwscan(struct net_device *dev, | ||
448 | struct iw_request_info *info, | ||
449 | struct iw_point *data, char *extra) | ||
450 | { | ||
451 | int res; | ||
452 | struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); | ||
453 | struct ieee80211_sub_if_data *sdata; | ||
454 | |||
455 | sdata = IEEE80211_DEV_TO_SUB_IF(dev); | ||
456 | |||
457 | if (local->sw_scanning || local->hw_scanning) | ||
458 | return -EAGAIN; | ||
459 | |||
460 | res = ieee80211_scan_results(local, info, extra, data->length); | ||
461 | if (res >= 0) { | ||
462 | data->length = res; | ||
463 | return 0; | ||
464 | } | ||
465 | data->length = 0; | ||
466 | return res; | ||
467 | } | ||
468 | |||
469 | |||
470 | static int ieee80211_ioctl_siwrate(struct net_device *dev, | 419 | static int ieee80211_ioctl_siwrate(struct net_device *dev, |
471 | struct iw_request_info *info, | 420 | struct iw_request_info *info, |
472 | struct iw_param *rate, char *extra) | 421 | struct iw_param *rate, char *extra) |
@@ -1165,8 +1114,8 @@ static const iw_handler ieee80211_handler[] = | |||
1165 | (iw_handler) ieee80211_ioctl_giwap, /* SIOCGIWAP */ | 1114 | (iw_handler) ieee80211_ioctl_giwap, /* SIOCGIWAP */ |
1166 | (iw_handler) ieee80211_ioctl_siwmlme, /* SIOCSIWMLME */ | 1115 | (iw_handler) ieee80211_ioctl_siwmlme, /* SIOCSIWMLME */ |
1167 | (iw_handler) NULL, /* SIOCGIWAPLIST */ | 1116 | (iw_handler) NULL, /* SIOCGIWAPLIST */ |
1168 | (iw_handler) ieee80211_ioctl_siwscan, /* SIOCSIWSCAN */ | 1117 | (iw_handler) cfg80211_wext_siwscan, /* SIOCSIWSCAN */ |
1169 | (iw_handler) ieee80211_ioctl_giwscan, /* SIOCGIWSCAN */ | 1118 | (iw_handler) cfg80211_wext_giwscan, /* SIOCGIWSCAN */ |
1170 | (iw_handler) ieee80211_ioctl_siwessid, /* SIOCSIWESSID */ | 1119 | (iw_handler) ieee80211_ioctl_siwessid, /* SIOCSIWESSID */ |
1171 | (iw_handler) ieee80211_ioctl_giwessid, /* SIOCGIWESSID */ | 1120 | (iw_handler) ieee80211_ioctl_giwessid, /* SIOCGIWESSID */ |
1172 | (iw_handler) NULL, /* SIOCSIWNICKN */ | 1121 | (iw_handler) NULL, /* SIOCSIWNICKN */ |
diff --git a/net/wireless/Makefile b/net/wireless/Makefile index 938a334c8dbc..dad43c24f695 100644 --- a/net/wireless/Makefile +++ b/net/wireless/Makefile | |||
@@ -5,7 +5,7 @@ obj-$(CONFIG_LIB80211_CRYPT_WEP) += lib80211_crypt_wep.o | |||
5 | obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o | 5 | obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o |
6 | obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o | 6 | obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o |
7 | 7 | ||
8 | cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o | 8 | cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o |
9 | cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o | 9 | cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o |
10 | cfg80211-$(CONFIG_NL80211) += nl80211.o | 10 | cfg80211-$(CONFIG_NL80211) += nl80211.o |
11 | 11 | ||
diff --git a/net/wireless/core.c b/net/wireless/core.c index 125226476089..3cccd1390cea 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c | |||
@@ -240,6 +240,8 @@ struct wiphy *wiphy_new(struct cfg80211_ops *ops, int sizeof_priv) | |||
240 | mutex_init(&drv->mtx); | 240 | mutex_init(&drv->mtx); |
241 | mutex_init(&drv->devlist_mtx); | 241 | mutex_init(&drv->devlist_mtx); |
242 | INIT_LIST_HEAD(&drv->netdev_list); | 242 | INIT_LIST_HEAD(&drv->netdev_list); |
243 | spin_lock_init(&drv->bss_lock); | ||
244 | INIT_LIST_HEAD(&drv->bss_list); | ||
243 | 245 | ||
244 | device_initialize(&drv->wiphy.dev); | 246 | device_initialize(&drv->wiphy.dev); |
245 | drv->wiphy.dev.class = &ieee80211_class; | 247 | drv->wiphy.dev.class = &ieee80211_class; |
@@ -259,6 +261,9 @@ int wiphy_register(struct wiphy *wiphy) | |||
259 | int i; | 261 | int i; |
260 | u16 ifmodes = wiphy->interface_modes; | 262 | u16 ifmodes = wiphy->interface_modes; |
261 | 263 | ||
264 | if (WARN_ON(wiphy->max_scan_ssids < 1)) | ||
265 | return -EINVAL; | ||
266 | |||
262 | /* sanity check ifmodes */ | 267 | /* sanity check ifmodes */ |
263 | WARN_ON(!ifmodes); | 268 | WARN_ON(!ifmodes); |
264 | ifmodes &= ((1 << __NL80211_IFTYPE_AFTER_LAST) - 1) & ~1; | 269 | ifmodes &= ((1 << __NL80211_IFTYPE_AFTER_LAST) - 1) & ~1; |
@@ -367,8 +372,11 @@ EXPORT_SYMBOL(wiphy_unregister); | |||
367 | 372 | ||
368 | void cfg80211_dev_free(struct cfg80211_registered_device *drv) | 373 | void cfg80211_dev_free(struct cfg80211_registered_device *drv) |
369 | { | 374 | { |
375 | struct cfg80211_internal_bss *scan, *tmp; | ||
370 | mutex_destroy(&drv->mtx); | 376 | mutex_destroy(&drv->mtx); |
371 | mutex_destroy(&drv->devlist_mtx); | 377 | mutex_destroy(&drv->devlist_mtx); |
378 | list_for_each_entry_safe(scan, tmp, &drv->bss_list, list) | ||
379 | kfree(scan); | ||
372 | kfree(drv); | 380 | kfree(drv); |
373 | } | 381 | } |
374 | 382 | ||
diff --git a/net/wireless/core.h b/net/wireless/core.h index f7fb9f413028..e29ad4cd464f 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h | |||
@@ -8,6 +8,8 @@ | |||
8 | #include <linux/mutex.h> | 8 | #include <linux/mutex.h> |
9 | #include <linux/list.h> | 9 | #include <linux/list.h> |
10 | #include <linux/netdevice.h> | 10 | #include <linux/netdevice.h> |
11 | #include <linux/kref.h> | ||
12 | #include <linux/rbtree.h> | ||
11 | #include <net/genetlink.h> | 13 | #include <net/genetlink.h> |
12 | #include <net/wireless.h> | 14 | #include <net/wireless.h> |
13 | #include <net/cfg80211.h> | 15 | #include <net/cfg80211.h> |
@@ -41,6 +43,13 @@ struct cfg80211_registered_device { | |||
41 | struct mutex devlist_mtx; | 43 | struct mutex devlist_mtx; |
42 | struct list_head netdev_list; | 44 | struct list_head netdev_list; |
43 | 45 | ||
46 | /* BSSes/scanning */ | ||
47 | spinlock_t bss_lock; | ||
48 | struct list_head bss_list; | ||
49 | struct rb_root bss_tree; | ||
50 | u32 bss_generation; | ||
51 | struct cfg80211_scan_request *scan_req; /* protected by RTNL */ | ||
52 | |||
44 | /* must be last because of the way we do wiphy_priv(), | 53 | /* must be last because of the way we do wiphy_priv(), |
45 | * and it should at least be aligned to NETDEV_ALIGN */ | 54 | * and it should at least be aligned to NETDEV_ALIGN */ |
46 | struct wiphy wiphy __attribute__((__aligned__(NETDEV_ALIGN))); | 55 | struct wiphy wiphy __attribute__((__aligned__(NETDEV_ALIGN))); |
@@ -56,6 +65,15 @@ struct cfg80211_registered_device *wiphy_to_dev(struct wiphy *wiphy) | |||
56 | extern struct mutex cfg80211_drv_mutex; | 65 | extern struct mutex cfg80211_drv_mutex; |
57 | extern struct list_head cfg80211_drv_list; | 66 | extern struct list_head cfg80211_drv_list; |
58 | 67 | ||
68 | struct cfg80211_internal_bss { | ||
69 | struct list_head list; | ||
70 | struct rb_node rbn; | ||
71 | unsigned long ts; | ||
72 | struct kref ref; | ||
73 | /* must be last because of priv member */ | ||
74 | struct cfg80211_bss pub; | ||
75 | }; | ||
76 | |||
59 | /* | 77 | /* |
60 | * This function returns a pointer to the driver | 78 | * This function returns a pointer to the driver |
61 | * that the genl_info item that is passed refers to. | 79 | * that the genl_info item that is passed refers to. |
@@ -94,4 +112,6 @@ extern int cfg80211_dev_rename(struct cfg80211_registered_device *drv, | |||
94 | void ieee80211_set_bitrate_flags(struct wiphy *wiphy); | 112 | void ieee80211_set_bitrate_flags(struct wiphy *wiphy); |
95 | void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby); | 113 | void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby); |
96 | 114 | ||
115 | void cfg80211_bss_expire(struct cfg80211_registered_device *dev); | ||
116 | |||
97 | #endif /* __NET_WIRELESS_CORE_H */ | 117 | #endif /* __NET_WIRELESS_CORE_H */ |
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d452396006ee..298a4de59948 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c | |||
@@ -14,6 +14,7 @@ | |||
14 | #include <linux/nl80211.h> | 14 | #include <linux/nl80211.h> |
15 | #include <linux/rtnetlink.h> | 15 | #include <linux/rtnetlink.h> |
16 | #include <linux/netlink.h> | 16 | #include <linux/netlink.h> |
17 | #include <linux/etherdevice.h> | ||
17 | #include <net/genetlink.h> | 18 | #include <net/genetlink.h> |
18 | #include <net/cfg80211.h> | 19 | #include <net/cfg80211.h> |
19 | #include "core.h" | 20 | #include "core.h" |
@@ -109,6 +110,8 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { | |||
109 | [NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 }, | 110 | [NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 }, |
110 | [NL80211_ATTR_IE] = { .type = NLA_BINARY, | 111 | [NL80211_ATTR_IE] = { .type = NLA_BINARY, |
111 | .len = IEEE80211_MAX_DATA_LEN }, | 112 | .len = IEEE80211_MAX_DATA_LEN }, |
113 | [NL80211_ATTR_SCAN_FREQUENCIES] = { .type = NLA_NESTED }, | ||
114 | [NL80211_ATTR_SCAN_SSIDS] = { .type = NLA_NESTED }, | ||
112 | }; | 115 | }; |
113 | 116 | ||
114 | /* message building helper */ | 117 | /* message building helper */ |
@@ -141,6 +144,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, | |||
141 | 144 | ||
142 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx); | 145 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx); |
143 | NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy)); | 146 | NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy)); |
147 | NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS, | ||
148 | dev->wiphy.max_scan_ssids); | ||
144 | 149 | ||
145 | nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES); | 150 | nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES); |
146 | if (!nl_modes) | 151 | if (!nl_modes) |
@@ -2270,6 +2275,246 @@ static int nl80211_set_mgmt_extra_ie(struct sk_buff *skb, | |||
2270 | return err; | 2275 | return err; |
2271 | } | 2276 | } |
2272 | 2277 | ||
2278 | static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) | ||
2279 | { | ||
2280 | struct cfg80211_registered_device *drv; | ||
2281 | struct net_device *dev; | ||
2282 | struct cfg80211_scan_request *request; | ||
2283 | struct cfg80211_ssid *ssid; | ||
2284 | struct ieee80211_channel *channel; | ||
2285 | struct nlattr *attr; | ||
2286 | struct wiphy *wiphy; | ||
2287 | int err, tmp, n_ssids = 0, n_channels = 0, i; | ||
2288 | enum ieee80211_band band; | ||
2289 | |||
2290 | err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev); | ||
2291 | if (err) | ||
2292 | return err; | ||
2293 | |||
2294 | wiphy = &drv->wiphy; | ||
2295 | |||
2296 | if (!drv->ops->scan) { | ||
2297 | err = -EOPNOTSUPP; | ||
2298 | goto out; | ||
2299 | } | ||
2300 | |||
2301 | rtnl_lock(); | ||
2302 | |||
2303 | if (drv->scan_req) { | ||
2304 | err = -EBUSY; | ||
2305 | goto out_unlock; | ||
2306 | } | ||
2307 | |||
2308 | if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { | ||
2309 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) | ||
2310 | n_channels++; | ||
2311 | if (!n_channels) { | ||
2312 | err = -EINVAL; | ||
2313 | goto out_unlock; | ||
2314 | } | ||
2315 | } else { | ||
2316 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) | ||
2317 | if (wiphy->bands[band]) | ||
2318 | n_channels += wiphy->bands[band]->n_channels; | ||
2319 | } | ||
2320 | |||
2321 | if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) | ||
2322 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) | ||
2323 | n_ssids++; | ||
2324 | |||
2325 | if (n_ssids > wiphy->max_scan_ssids) { | ||
2326 | err = -EINVAL; | ||
2327 | goto out_unlock; | ||
2328 | } | ||
2329 | |||
2330 | request = kzalloc(sizeof(*request) | ||
2331 | + sizeof(*ssid) * n_ssids | ||
2332 | + sizeof(channel) * n_channels, GFP_KERNEL); | ||
2333 | if (!request) { | ||
2334 | err = -ENOMEM; | ||
2335 | goto out_unlock; | ||
2336 | } | ||
2337 | |||
2338 | request->channels = (void *)((char *)request + sizeof(*request)); | ||
2339 | request->n_channels = n_channels; | ||
2340 | if (n_ssids) | ||
2341 | request->ssids = (void *)(request->channels + n_channels); | ||
2342 | request->n_ssids = n_ssids; | ||
2343 | |||
2344 | if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { | ||
2345 | /* user specified, bail out if channel not found */ | ||
2346 | request->n_channels = n_channels; | ||
2347 | i = 0; | ||
2348 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) { | ||
2349 | request->channels[i] = ieee80211_get_channel(wiphy, nla_get_u32(attr)); | ||
2350 | if (!request->channels[i]) { | ||
2351 | err = -EINVAL; | ||
2352 | goto out_free; | ||
2353 | } | ||
2354 | i++; | ||
2355 | } | ||
2356 | } else { | ||
2357 | /* all channels */ | ||
2358 | i = 0; | ||
2359 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | ||
2360 | int j; | ||
2361 | if (!wiphy->bands[band]) | ||
2362 | continue; | ||
2363 | for (j = 0; j < wiphy->bands[band]->n_channels; j++) { | ||
2364 | request->channels[i] = &wiphy->bands[band]->channels[j]; | ||
2365 | i++; | ||
2366 | } | ||
2367 | } | ||
2368 | } | ||
2369 | |||
2370 | i = 0; | ||
2371 | if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) { | ||
2372 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) { | ||
2373 | if (request->ssids[i].ssid_len > IEEE80211_MAX_SSID_LEN) { | ||
2374 | err = -EINVAL; | ||
2375 | goto out_free; | ||
2376 | } | ||
2377 | memcpy(request->ssids[i].ssid, nla_data(attr), nla_len(attr)); | ||
2378 | request->ssids[i].ssid_len = nla_len(attr); | ||
2379 | i++; | ||
2380 | } | ||
2381 | } | ||
2382 | |||
2383 | request->ifidx = dev->ifindex; | ||
2384 | request->wiphy = &drv->wiphy; | ||
2385 | |||
2386 | drv->scan_req = request; | ||
2387 | err = drv->ops->scan(&drv->wiphy, dev, request); | ||
2388 | |||
2389 | out_free: | ||
2390 | if (err) { | ||
2391 | drv->scan_req = NULL; | ||
2392 | kfree(request); | ||
2393 | } | ||
2394 | out_unlock: | ||
2395 | rtnl_unlock(); | ||
2396 | out: | ||
2397 | cfg80211_put_dev(drv); | ||
2398 | dev_put(dev); | ||
2399 | return err; | ||
2400 | } | ||
2401 | |||
2402 | static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags, | ||
2403 | struct cfg80211_registered_device *rdev, | ||
2404 | struct net_device *dev, | ||
2405 | struct cfg80211_bss *res) | ||
2406 | { | ||
2407 | void *hdr; | ||
2408 | struct nlattr *bss; | ||
2409 | |||
2410 | hdr = nl80211hdr_put(msg, pid, seq, flags, | ||
2411 | NL80211_CMD_NEW_SCAN_RESULTS); | ||
2412 | if (!hdr) | ||
2413 | return -1; | ||
2414 | |||
2415 | NLA_PUT_U32(msg, NL80211_ATTR_SCAN_GENERATION, | ||
2416 | rdev->bss_generation); | ||
2417 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex); | ||
2418 | |||
2419 | bss = nla_nest_start(msg, NL80211_ATTR_BSS); | ||
2420 | if (!bss) | ||
2421 | goto nla_put_failure; | ||
2422 | if (!is_zero_ether_addr(res->bssid)) | ||
2423 | NLA_PUT(msg, NL80211_BSS_BSSID, ETH_ALEN, res->bssid); | ||
2424 | if (res->information_elements && res->len_information_elements) | ||
2425 | NLA_PUT(msg, NL80211_BSS_INFORMATION_ELEMENTS, | ||
2426 | res->len_information_elements, | ||
2427 | res->information_elements); | ||
2428 | if (res->tsf) | ||
2429 | NLA_PUT_U64(msg, NL80211_BSS_TSF, res->tsf); | ||
2430 | if (res->beacon_interval) | ||
2431 | NLA_PUT_U16(msg, NL80211_BSS_BEACON_INTERVAL, res->beacon_interval); | ||
2432 | NLA_PUT_U16(msg, NL80211_BSS_CAPABILITY, res->capability); | ||
2433 | NLA_PUT_U32(msg, NL80211_BSS_FREQUENCY, res->channel->center_freq); | ||
2434 | |||
2435 | switch (res->signal_type) { | ||
2436 | case CFG80211_SIGNAL_TYPE_MBM: | ||
2437 | NLA_PUT_U32(msg, NL80211_BSS_SIGNAL_MBM, res->signal); | ||
2438 | break; | ||
2439 | case CFG80211_SIGNAL_TYPE_UNSPEC: | ||
2440 | NLA_PUT_U8(msg, NL80211_BSS_SIGNAL_UNSPEC, res->signal); | ||
2441 | break; | ||
2442 | default: | ||
2443 | break; | ||
2444 | } | ||
2445 | |||
2446 | nla_nest_end(msg, bss); | ||
2447 | |||
2448 | return genlmsg_end(msg, hdr); | ||
2449 | |||
2450 | nla_put_failure: | ||
2451 | genlmsg_cancel(msg, hdr); | ||
2452 | return -EMSGSIZE; | ||
2453 | } | ||
2454 | |||
2455 | static int nl80211_dump_scan(struct sk_buff *skb, | ||
2456 | struct netlink_callback *cb) | ||
2457 | { | ||
2458 | struct cfg80211_registered_device *dev; | ||
2459 | struct net_device *netdev; | ||
2460 | struct cfg80211_internal_bss *scan; | ||
2461 | int ifidx = cb->args[0]; | ||
2462 | int start = cb->args[1], idx = 0; | ||
2463 | int err; | ||
2464 | |||
2465 | if (!ifidx) { | ||
2466 | err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize, | ||
2467 | nl80211_fam.attrbuf, nl80211_fam.maxattr, | ||
2468 | nl80211_policy); | ||
2469 | if (err) | ||
2470 | return err; | ||
2471 | |||
2472 | if (!nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]) | ||
2473 | return -EINVAL; | ||
2474 | |||
2475 | ifidx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]); | ||
2476 | if (!ifidx) | ||
2477 | return -EINVAL; | ||
2478 | cb->args[0] = ifidx; | ||
2479 | } | ||
2480 | |||
2481 | netdev = dev_get_by_index(&init_net, ifidx); | ||
2482 | if (!netdev) | ||
2483 | return -ENODEV; | ||
2484 | |||
2485 | dev = cfg80211_get_dev_from_ifindex(ifidx); | ||
2486 | if (IS_ERR(dev)) { | ||
2487 | err = PTR_ERR(dev); | ||
2488 | goto out_put_netdev; | ||
2489 | } | ||
2490 | |||
2491 | spin_lock_bh(&dev->bss_lock); | ||
2492 | cfg80211_bss_expire(dev); | ||
2493 | |||
2494 | list_for_each_entry(scan, &dev->bss_list, list) { | ||
2495 | if (++idx <= start) | ||
2496 | continue; | ||
2497 | if (nl80211_send_bss(skb, | ||
2498 | NETLINK_CB(cb->skb).pid, | ||
2499 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
2500 | dev, netdev, &scan->pub) < 0) { | ||
2501 | idx--; | ||
2502 | goto out; | ||
2503 | } | ||
2504 | } | ||
2505 | |||
2506 | out: | ||
2507 | spin_unlock_bh(&dev->bss_lock); | ||
2508 | |||
2509 | cb->args[1] = idx; | ||
2510 | err = skb->len; | ||
2511 | cfg80211_put_dev(dev); | ||
2512 | out_put_netdev: | ||
2513 | dev_put(netdev); | ||
2514 | |||
2515 | return err; | ||
2516 | } | ||
2517 | |||
2273 | static struct genl_ops nl80211_ops[] = { | 2518 | static struct genl_ops nl80211_ops[] = { |
2274 | { | 2519 | { |
2275 | .cmd = NL80211_CMD_GET_WIPHY, | 2520 | .cmd = NL80211_CMD_GET_WIPHY, |
@@ -2443,12 +2688,26 @@ static struct genl_ops nl80211_ops[] = { | |||
2443 | .policy = nl80211_policy, | 2688 | .policy = nl80211_policy, |
2444 | .flags = GENL_ADMIN_PERM, | 2689 | .flags = GENL_ADMIN_PERM, |
2445 | }, | 2690 | }, |
2691 | { | ||
2692 | .cmd = NL80211_CMD_TRIGGER_SCAN, | ||
2693 | .doit = nl80211_trigger_scan, | ||
2694 | .policy = nl80211_policy, | ||
2695 | .flags = GENL_ADMIN_PERM, | ||
2696 | }, | ||
2697 | { | ||
2698 | .cmd = NL80211_CMD_GET_SCAN, | ||
2699 | .policy = nl80211_policy, | ||
2700 | .dumpit = nl80211_dump_scan, | ||
2701 | }, | ||
2446 | }; | 2702 | }; |
2447 | 2703 | ||
2448 | /* multicast groups */ | 2704 | /* multicast groups */ |
2449 | static struct genl_multicast_group nl80211_config_mcgrp = { | 2705 | static struct genl_multicast_group nl80211_config_mcgrp = { |
2450 | .name = "config", | 2706 | .name = "config", |
2451 | }; | 2707 | }; |
2708 | static struct genl_multicast_group nl80211_scan_mcgrp = { | ||
2709 | .name = "scan", | ||
2710 | }; | ||
2452 | 2711 | ||
2453 | /* notification functions */ | 2712 | /* notification functions */ |
2454 | 2713 | ||
@@ -2468,6 +2727,66 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev) | |||
2468 | genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); | 2727 | genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); |
2469 | } | 2728 | } |
2470 | 2729 | ||
2730 | static int nl80211_send_scan_donemsg(struct sk_buff *msg, | ||
2731 | struct cfg80211_registered_device *rdev, | ||
2732 | struct net_device *netdev, | ||
2733 | u32 pid, u32 seq, int flags, | ||
2734 | u32 cmd) | ||
2735 | { | ||
2736 | void *hdr; | ||
2737 | |||
2738 | hdr = nl80211hdr_put(msg, pid, seq, flags, cmd); | ||
2739 | if (!hdr) | ||
2740 | return -1; | ||
2741 | |||
2742 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->idx); | ||
2743 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex); | ||
2744 | |||
2745 | /* XXX: we should probably bounce back the request? */ | ||
2746 | |||
2747 | return genlmsg_end(msg, hdr); | ||
2748 | |||
2749 | nla_put_failure: | ||
2750 | genlmsg_cancel(msg, hdr); | ||
2751 | return -EMSGSIZE; | ||
2752 | } | ||
2753 | |||
2754 | void nl80211_send_scan_done(struct cfg80211_registered_device *rdev, | ||
2755 | struct net_device *netdev) | ||
2756 | { | ||
2757 | struct sk_buff *msg; | ||
2758 | |||
2759 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
2760 | if (!msg) | ||
2761 | return; | ||
2762 | |||
2763 | if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0, | ||
2764 | NL80211_CMD_NEW_SCAN_RESULTS) < 0) { | ||
2765 | nlmsg_free(msg); | ||
2766 | return; | ||
2767 | } | ||
2768 | |||
2769 | genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); | ||
2770 | } | ||
2771 | |||
2772 | void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, | ||
2773 | struct net_device *netdev) | ||
2774 | { | ||
2775 | struct sk_buff *msg; | ||
2776 | |||
2777 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
2778 | if (!msg) | ||
2779 | return; | ||
2780 | |||
2781 | if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0, | ||
2782 | NL80211_CMD_SCAN_ABORTED) < 0) { | ||
2783 | nlmsg_free(msg); | ||
2784 | return; | ||
2785 | } | ||
2786 | |||
2787 | genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); | ||
2788 | } | ||
2789 | |||
2471 | /* initialisation/exit functions */ | 2790 | /* initialisation/exit functions */ |
2472 | 2791 | ||
2473 | int nl80211_init(void) | 2792 | int nl80211_init(void) |
@@ -2488,6 +2807,10 @@ int nl80211_init(void) | |||
2488 | if (err) | 2807 | if (err) |
2489 | goto err_out; | 2808 | goto err_out; |
2490 | 2809 | ||
2810 | err = genl_register_mc_group(&nl80211_fam, &nl80211_scan_mcgrp); | ||
2811 | if (err) | ||
2812 | goto err_out; | ||
2813 | |||
2491 | return 0; | 2814 | return 0; |
2492 | err_out: | 2815 | err_out: |
2493 | genl_unregister_family(&nl80211_fam); | 2816 | genl_unregister_family(&nl80211_fam); |
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h index f3ea5c029aee..b565a5f84e97 100644 --- a/net/wireless/nl80211.h +++ b/net/wireless/nl80211.h | |||
@@ -7,6 +7,10 @@ | |||
7 | extern int nl80211_init(void); | 7 | extern int nl80211_init(void); |
8 | extern void nl80211_exit(void); | 8 | extern void nl80211_exit(void); |
9 | extern void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev); | 9 | extern void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev); |
10 | extern void nl80211_send_scan_done(struct cfg80211_registered_device *rdev, | ||
11 | struct net_device *netdev); | ||
12 | extern void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, | ||
13 | struct net_device *netdev); | ||
10 | #else | 14 | #else |
11 | static inline int nl80211_init(void) | 15 | static inline int nl80211_init(void) |
12 | { | 16 | { |
@@ -19,6 +23,10 @@ static inline void nl80211_notify_dev_rename( | |||
19 | struct cfg80211_registered_device *rdev) | 23 | struct cfg80211_registered_device *rdev) |
20 | { | 24 | { |
21 | } | 25 | } |
26 | static inline void | ||
27 | nl80211_send_scan_done(struct cfg80211_registered_device *rdev, | ||
28 | struct net_device *netdev) | ||
29 | {} | ||
22 | #endif /* CONFIG_NL80211 */ | 30 | #endif /* CONFIG_NL80211 */ |
23 | 31 | ||
24 | #endif /* __NET_WIRELESS_NL80211_H */ | 32 | #endif /* __NET_WIRELESS_NL80211_H */ |
diff --git a/net/wireless/scan.c b/net/wireless/scan.c new file mode 100644 index 000000000000..009d12810c55 --- /dev/null +++ b/net/wireless/scan.c | |||
@@ -0,0 +1,807 @@ | |||
1 | /* | ||
2 | * cfg80211 scan result handling | ||
3 | * | ||
4 | * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> | ||
5 | */ | ||
6 | #include <linux/kernel.h> | ||
7 | #include <linux/module.h> | ||
8 | #include <linux/netdevice.h> | ||
9 | #include <linux/wireless.h> | ||
10 | #include <linux/nl80211.h> | ||
11 | #include <linux/etherdevice.h> | ||
12 | #include <net/arp.h> | ||
13 | #include <net/cfg80211.h> | ||
14 | #include <net/iw_handler.h> | ||
15 | #include "core.h" | ||
16 | #include "nl80211.h" | ||
17 | |||
18 | #define IEEE80211_SCAN_RESULT_EXPIRE (10 * HZ) | ||
19 | |||
20 | void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted) | ||
21 | { | ||
22 | struct net_device *dev; | ||
23 | #ifdef CONFIG_WIRELESS_EXT | ||
24 | union iwreq_data wrqu; | ||
25 | #endif | ||
26 | |||
27 | dev = dev_get_by_index(&init_net, request->ifidx); | ||
28 | if (!dev) | ||
29 | goto out; | ||
30 | |||
31 | WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req); | ||
32 | wiphy_to_dev(request->wiphy)->scan_req = NULL; | ||
33 | |||
34 | if (aborted) | ||
35 | nl80211_send_scan_aborted(wiphy_to_dev(request->wiphy), dev); | ||
36 | else | ||
37 | nl80211_send_scan_done(wiphy_to_dev(request->wiphy), dev); | ||
38 | |||
39 | #ifdef CONFIG_WIRELESS_EXT | ||
40 | if (!aborted) { | ||
41 | memset(&wrqu, 0, sizeof(wrqu)); | ||
42 | |||
43 | wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL); | ||
44 | } | ||
45 | #endif | ||
46 | |||
47 | dev_put(dev); | ||
48 | |||
49 | out: | ||
50 | kfree(request); | ||
51 | } | ||
52 | EXPORT_SYMBOL(cfg80211_scan_done); | ||
53 | |||
54 | static void bss_release(struct kref *ref) | ||
55 | { | ||
56 | struct cfg80211_internal_bss *bss; | ||
57 | |||
58 | bss = container_of(ref, struct cfg80211_internal_bss, ref); | ||
59 | kfree(bss); | ||
60 | } | ||
61 | |||
62 | /* must hold dev->bss_lock! */ | ||
63 | void cfg80211_bss_expire(struct cfg80211_registered_device *dev) | ||
64 | { | ||
65 | struct cfg80211_internal_bss *bss, *tmp; | ||
66 | bool expired = false; | ||
67 | |||
68 | list_for_each_entry_safe(bss, tmp, &dev->bss_list, list) { | ||
69 | if (!time_after(jiffies, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE)) | ||
70 | continue; | ||
71 | list_del(&bss->list); | ||
72 | rb_erase(&bss->rbn, &dev->bss_tree); | ||
73 | kref_put(&bss->ref, bss_release); | ||
74 | expired = true; | ||
75 | } | ||
76 | |||
77 | if (expired) | ||
78 | dev->bss_generation++; | ||
79 | } | ||
80 | |||
81 | static u8 *find_ie(u8 num, u8 *ies, size_t len) | ||
82 | { | ||
83 | while (len > 2 && ies[0] != num) { | ||
84 | len -= ies[1] + 2; | ||
85 | ies += ies[1] + 2; | ||
86 | } | ||
87 | if (len < 2) | ||
88 | return NULL; | ||
89 | if (len < 2 + ies[1]) | ||
90 | return NULL; | ||
91 | return ies; | ||
92 | } | ||
93 | |||
94 | static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2) | ||
95 | { | ||
96 | const u8 *ie1 = find_ie(num, ies1, len1); | ||
97 | const u8 *ie2 = find_ie(num, ies2, len2); | ||
98 | int r; | ||
99 | |||
100 | if (!ie1 && !ie2) | ||
101 | return 0; | ||
102 | if (!ie1) | ||
103 | return -1; | ||
104 | |||
105 | r = memcmp(ie1 + 2, ie2 + 2, min(ie1[1], ie2[1])); | ||
106 | if (r == 0 && ie1[1] != ie2[1]) | ||
107 | return ie2[1] - ie1[1]; | ||
108 | return r; | ||
109 | } | ||
110 | |||
111 | static bool is_bss(struct cfg80211_bss *a, | ||
112 | const u8 *bssid, | ||
113 | const u8 *ssid, size_t ssid_len) | ||
114 | { | ||
115 | const u8 *ssidie; | ||
116 | |||
117 | if (compare_ether_addr(a->bssid, bssid)) | ||
118 | return false; | ||
119 | |||
120 | ssidie = find_ie(WLAN_EID_SSID, | ||
121 | a->information_elements, | ||
122 | a->len_information_elements); | ||
123 | if (!ssidie) | ||
124 | return false; | ||
125 | if (ssidie[1] != ssid_len) | ||
126 | return false; | ||
127 | return memcmp(ssidie + 2, ssid, ssid_len) == 0; | ||
128 | } | ||
129 | |||
130 | static bool is_mesh(struct cfg80211_bss *a, | ||
131 | const u8 *meshid, size_t meshidlen, | ||
132 | const u8 *meshcfg) | ||
133 | { | ||
134 | const u8 *ie; | ||
135 | |||
136 | if (!is_zero_ether_addr(a->bssid)) | ||
137 | return false; | ||
138 | |||
139 | ie = find_ie(WLAN_EID_MESH_ID, | ||
140 | a->information_elements, | ||
141 | a->len_information_elements); | ||
142 | if (!ie) | ||
143 | return false; | ||
144 | if (ie[1] != meshidlen) | ||
145 | return false; | ||
146 | if (memcmp(ie + 2, meshid, meshidlen)) | ||
147 | return false; | ||
148 | |||
149 | ie = find_ie(WLAN_EID_MESH_CONFIG, | ||
150 | a->information_elements, | ||
151 | a->len_information_elements); | ||
152 | if (ie[1] != IEEE80211_MESH_CONFIG_LEN) | ||
153 | return false; | ||
154 | |||
155 | /* | ||
156 | * Ignore mesh capability (last two bytes of the IE) when | ||
157 | * comparing since that may differ between stations taking | ||
158 | * part in the same mesh. | ||
159 | */ | ||
160 | return memcmp(ie + 2, meshcfg, IEEE80211_MESH_CONFIG_LEN - 2) == 0; | ||
161 | } | ||
162 | |||
163 | static int cmp_bss(struct cfg80211_bss *a, | ||
164 | struct cfg80211_bss *b) | ||
165 | { | ||
166 | int r; | ||
167 | |||
168 | if (a->channel != b->channel) | ||
169 | return b->channel->center_freq - a->channel->center_freq; | ||
170 | |||
171 | r = memcmp(a->bssid, b->bssid, ETH_ALEN); | ||
172 | if (r) | ||
173 | return r; | ||
174 | |||
175 | if (is_zero_ether_addr(a->bssid)) { | ||
176 | r = cmp_ies(WLAN_EID_MESH_ID, | ||
177 | a->information_elements, | ||
178 | a->len_information_elements, | ||
179 | b->information_elements, | ||
180 | b->len_information_elements); | ||
181 | if (r) | ||
182 | return r; | ||
183 | return cmp_ies(WLAN_EID_MESH_CONFIG, | ||
184 | a->information_elements, | ||
185 | a->len_information_elements, | ||
186 | b->information_elements, | ||
187 | b->len_information_elements); | ||
188 | } | ||
189 | |||
190 | return cmp_ies(WLAN_EID_SSID, | ||
191 | a->information_elements, | ||
192 | a->len_information_elements, | ||
193 | b->information_elements, | ||
194 | b->len_information_elements); | ||
195 | } | ||
196 | |||
197 | struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy, | ||
198 | struct ieee80211_channel *channel, | ||
199 | const u8 *bssid, | ||
200 | const u8 *ssid, size_t ssid_len) | ||
201 | { | ||
202 | struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy); | ||
203 | struct cfg80211_internal_bss *bss, *res = NULL; | ||
204 | |||
205 | spin_lock_bh(&dev->bss_lock); | ||
206 | |||
207 | list_for_each_entry(bss, &dev->bss_list, list) { | ||
208 | if (channel && bss->pub.channel != channel) | ||
209 | continue; | ||
210 | if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { | ||
211 | res = bss; | ||
212 | kref_get(&res->ref); | ||
213 | break; | ||
214 | } | ||
215 | } | ||
216 | |||
217 | spin_unlock_bh(&dev->bss_lock); | ||
218 | if (!res) | ||
219 | return NULL; | ||
220 | return &res->pub; | ||
221 | } | ||
222 | EXPORT_SYMBOL(cfg80211_get_bss); | ||
223 | |||
224 | struct cfg80211_bss *cfg80211_get_mesh(struct wiphy *wiphy, | ||
225 | struct ieee80211_channel *channel, | ||
226 | const u8 *meshid, size_t meshidlen, | ||
227 | const u8 *meshcfg) | ||
228 | { | ||
229 | struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy); | ||
230 | struct cfg80211_internal_bss *bss, *res = NULL; | ||
231 | |||
232 | spin_lock_bh(&dev->bss_lock); | ||
233 | |||
234 | list_for_each_entry(bss, &dev->bss_list, list) { | ||
235 | if (channel && bss->pub.channel != channel) | ||
236 | continue; | ||
237 | if (is_mesh(&bss->pub, meshid, meshidlen, meshcfg)) { | ||
238 | res = bss; | ||
239 | kref_get(&res->ref); | ||
240 | break; | ||
241 | } | ||
242 | } | ||
243 | |||
244 | spin_unlock_bh(&dev->bss_lock); | ||
245 | if (!res) | ||
246 | return NULL; | ||
247 | return &res->pub; | ||
248 | } | ||
249 | EXPORT_SYMBOL(cfg80211_get_mesh); | ||
250 | |||
251 | |||
252 | static void rb_insert_bss(struct cfg80211_registered_device *dev, | ||
253 | struct cfg80211_internal_bss *bss) | ||
254 | { | ||
255 | struct rb_node **p = &dev->bss_tree.rb_node; | ||
256 | struct rb_node *parent = NULL; | ||
257 | struct cfg80211_internal_bss *tbss; | ||
258 | int cmp; | ||
259 | |||
260 | while (*p) { | ||
261 | parent = *p; | ||
262 | tbss = rb_entry(parent, struct cfg80211_internal_bss, rbn); | ||
263 | |||
264 | cmp = cmp_bss(&bss->pub, &tbss->pub); | ||
265 | |||
266 | if (WARN_ON(!cmp)) { | ||
267 | /* will sort of leak this BSS */ | ||
268 | return; | ||
269 | } | ||
270 | |||
271 | if (cmp < 0) | ||
272 | p = &(*p)->rb_left; | ||
273 | else | ||
274 | p = &(*p)->rb_right; | ||
275 | } | ||
276 | |||
277 | rb_link_node(&bss->rbn, parent, p); | ||
278 | rb_insert_color(&bss->rbn, &dev->bss_tree); | ||
279 | } | ||
280 | |||
281 | static struct cfg80211_internal_bss * | ||
282 | rb_find_bss(struct cfg80211_registered_device *dev, | ||
283 | struct cfg80211_internal_bss *res) | ||
284 | { | ||
285 | struct rb_node *n = dev->bss_tree.rb_node; | ||
286 | struct cfg80211_internal_bss *bss; | ||
287 | int r; | ||
288 | |||
289 | while (n) { | ||
290 | bss = rb_entry(n, struct cfg80211_internal_bss, rbn); | ||
291 | r = cmp_bss(&res->pub, &bss->pub); | ||
292 | |||
293 | if (r == 0) | ||
294 | return bss; | ||
295 | else if (r < 0) | ||
296 | n = n->rb_left; | ||
297 | else | ||
298 | n = n->rb_right; | ||
299 | } | ||
300 | |||
301 | return NULL; | ||
302 | } | ||
303 | |||
304 | static struct cfg80211_internal_bss * | ||
305 | cfg80211_bss_update(struct cfg80211_registered_device *dev, | ||
306 | struct cfg80211_internal_bss *res, | ||
307 | bool overwrite) | ||
308 | { | ||
309 | struct cfg80211_internal_bss *found = NULL; | ||
310 | const u8 *meshid, *meshcfg; | ||
311 | |||
312 | /* | ||
313 | * The reference to "res" is donated to this function. | ||
314 | */ | ||
315 | |||
316 | if (WARN_ON(!res->pub.channel)) { | ||
317 | kref_put(&res->ref, bss_release); | ||
318 | return NULL; | ||
319 | } | ||
320 | |||
321 | res->ts = jiffies; | ||
322 | |||
323 | if (is_zero_ether_addr(res->pub.bssid)) { | ||
324 | /* must be mesh, verify */ | ||
325 | meshid = find_ie(WLAN_EID_MESH_ID, res->pub.information_elements, | ||
326 | res->pub.len_information_elements); | ||
327 | meshcfg = find_ie(WLAN_EID_MESH_CONFIG, | ||
328 | res->pub.information_elements, | ||
329 | res->pub.len_information_elements); | ||
330 | if (!meshid || !meshcfg || | ||
331 | meshcfg[1] != IEEE80211_MESH_CONFIG_LEN) { | ||
332 | /* bogus mesh */ | ||
333 | kref_put(&res->ref, bss_release); | ||
334 | return NULL; | ||
335 | } | ||
336 | } | ||
337 | |||
338 | spin_lock_bh(&dev->bss_lock); | ||
339 | |||
340 | found = rb_find_bss(dev, res); | ||
341 | |||
342 | if (found && overwrite) { | ||
343 | list_replace(&found->list, &res->list); | ||
344 | rb_replace_node(&found->rbn, &res->rbn, | ||
345 | &dev->bss_tree); | ||
346 | kref_put(&found->ref, bss_release); | ||
347 | found = res; | ||
348 | } else if (found) { | ||
349 | kref_get(&found->ref); | ||
350 | found->pub.beacon_interval = res->pub.beacon_interval; | ||
351 | found->pub.tsf = res->pub.tsf; | ||
352 | found->pub.signal = res->pub.signal; | ||
353 | found->pub.signal_type = res->pub.signal_type; | ||
354 | found->pub.capability = res->pub.capability; | ||
355 | found->ts = res->ts; | ||
356 | kref_put(&res->ref, bss_release); | ||
357 | } else { | ||
358 | /* this "consumes" the reference */ | ||
359 | list_add_tail(&res->list, &dev->bss_list); | ||
360 | rb_insert_bss(dev, res); | ||
361 | found = res; | ||
362 | } | ||
363 | |||
364 | dev->bss_generation++; | ||
365 | spin_unlock_bh(&dev->bss_lock); | ||
366 | |||
367 | kref_get(&found->ref); | ||
368 | return found; | ||
369 | } | ||
370 | |||
371 | struct cfg80211_bss * | ||
372 | cfg80211_inform_bss_frame(struct wiphy *wiphy, | ||
373 | struct ieee80211_channel *channel, | ||
374 | struct ieee80211_mgmt *mgmt, size_t len, | ||
375 | s32 signal, enum cfg80211_signal_type sigtype, | ||
376 | gfp_t gfp) | ||
377 | { | ||
378 | struct cfg80211_internal_bss *res; | ||
379 | size_t ielen = len - offsetof(struct ieee80211_mgmt, | ||
380 | u.probe_resp.variable); | ||
381 | bool overwrite; | ||
382 | size_t privsz = wiphy->bss_priv_size; | ||
383 | |||
384 | if (WARN_ON(sigtype == NL80211_BSS_SIGNAL_UNSPEC && | ||
385 | (signal < 0 || signal > 100))) | ||
386 | return NULL; | ||
387 | |||
388 | if (WARN_ON(!mgmt || !wiphy || | ||
389 | len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable))) | ||
390 | return NULL; | ||
391 | |||
392 | res = kzalloc(sizeof(*res) + privsz + ielen, gfp); | ||
393 | if (!res) | ||
394 | return NULL; | ||
395 | |||
396 | memcpy(res->pub.bssid, mgmt->bssid, ETH_ALEN); | ||
397 | res->pub.channel = channel; | ||
398 | res->pub.signal_type = sigtype; | ||
399 | res->pub.signal = signal; | ||
400 | res->pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); | ||
401 | res->pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); | ||
402 | res->pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); | ||
403 | /* point to after the private area */ | ||
404 | res->pub.information_elements = (u8 *)res + sizeof(*res) + privsz; | ||
405 | memcpy(res->pub.information_elements, mgmt->u.probe_resp.variable, ielen); | ||
406 | res->pub.len_information_elements = ielen; | ||
407 | |||
408 | kref_init(&res->ref); | ||
409 | |||
410 | overwrite = ieee80211_is_probe_resp(mgmt->frame_control); | ||
411 | |||
412 | res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, overwrite); | ||
413 | if (!res) | ||
414 | return NULL; | ||
415 | |||
416 | /* cfg80211_bss_update gives us a referenced result */ | ||
417 | return &res->pub; | ||
418 | } | ||
419 | EXPORT_SYMBOL(cfg80211_inform_bss_frame); | ||
420 | |||
421 | void cfg80211_put_bss(struct cfg80211_bss *pub) | ||
422 | { | ||
423 | struct cfg80211_internal_bss *bss; | ||
424 | |||
425 | if (!pub) | ||
426 | return; | ||
427 | |||
428 | bss = container_of(pub, struct cfg80211_internal_bss, pub); | ||
429 | kref_put(&bss->ref, bss_release); | ||
430 | } | ||
431 | EXPORT_SYMBOL(cfg80211_put_bss); | ||
432 | |||
433 | #ifdef CONFIG_WIRELESS_EXT | ||
434 | int cfg80211_wext_siwscan(struct net_device *dev, | ||
435 | struct iw_request_info *info, | ||
436 | union iwreq_data *wrqu, char *extra) | ||
437 | { | ||
438 | struct cfg80211_registered_device *rdev; | ||
439 | struct wiphy *wiphy; | ||
440 | struct iw_scan_req *wreq = NULL; | ||
441 | struct cfg80211_scan_request *creq; | ||
442 | int i, err, n_channels = 0; | ||
443 | enum ieee80211_band band; | ||
444 | |||
445 | if (!netif_running(dev)) | ||
446 | return -ENETDOWN; | ||
447 | |||
448 | rdev = cfg80211_get_dev_from_ifindex(dev->ifindex); | ||
449 | |||
450 | if (IS_ERR(rdev)) | ||
451 | return PTR_ERR(rdev); | ||
452 | |||
453 | if (rdev->scan_req) { | ||
454 | err = -EBUSY; | ||
455 | goto out; | ||
456 | } | ||
457 | |||
458 | wiphy = &rdev->wiphy; | ||
459 | |||
460 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) | ||
461 | if (wiphy->bands[band]) | ||
462 | n_channels += wiphy->bands[band]->n_channels; | ||
463 | |||
464 | creq = kzalloc(sizeof(*creq) + sizeof(struct cfg80211_ssid) + | ||
465 | n_channels * sizeof(void *), | ||
466 | GFP_ATOMIC); | ||
467 | if (!creq) { | ||
468 | err = -ENOMEM; | ||
469 | goto out; | ||
470 | } | ||
471 | |||
472 | creq->wiphy = wiphy; | ||
473 | creq->ifidx = dev->ifindex; | ||
474 | creq->ssids = (void *)(creq + 1); | ||
475 | creq->channels = (void *)(creq->ssids + 1); | ||
476 | creq->n_channels = n_channels; | ||
477 | creq->n_ssids = 1; | ||
478 | |||
479 | /* all channels */ | ||
480 | i = 0; | ||
481 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | ||
482 | int j; | ||
483 | if (!wiphy->bands[band]) | ||
484 | continue; | ||
485 | for (j = 0; j < wiphy->bands[band]->n_channels; j++) { | ||
486 | creq->channels[i] = &wiphy->bands[band]->channels[j]; | ||
487 | i++; | ||
488 | } | ||
489 | } | ||
490 | |||
491 | /* translate scan request */ | ||
492 | if (wrqu->data.length == sizeof(struct iw_scan_req)) { | ||
493 | wreq = (struct iw_scan_req *)extra; | ||
494 | |||
495 | if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { | ||
496 | if (wreq->essid_len > IEEE80211_MAX_SSID_LEN) | ||
497 | return -EINVAL; | ||
498 | memcpy(creq->ssids[0].ssid, wreq->essid, wreq->essid_len); | ||
499 | creq->ssids[0].ssid_len = wreq->essid_len; | ||
500 | } | ||
501 | if (wreq->scan_type == IW_SCAN_TYPE_PASSIVE) | ||
502 | creq->n_ssids = 0; | ||
503 | } | ||
504 | |||
505 | rdev->scan_req = creq; | ||
506 | err = rdev->ops->scan(wiphy, dev, creq); | ||
507 | if (err) { | ||
508 | rdev->scan_req = NULL; | ||
509 | kfree(creq); | ||
510 | } | ||
511 | out: | ||
512 | cfg80211_put_dev(rdev); | ||
513 | return err; | ||
514 | } | ||
515 | EXPORT_SYMBOL(cfg80211_wext_siwscan); | ||
516 | |||
517 | static void ieee80211_scan_add_ies(struct iw_request_info *info, | ||
518 | struct cfg80211_bss *bss, | ||
519 | char **current_ev, char *end_buf) | ||
520 | { | ||
521 | u8 *pos, *end, *next; | ||
522 | struct iw_event iwe; | ||
523 | |||
524 | if (!bss->information_elements || | ||
525 | !bss->len_information_elements) | ||
526 | return; | ||
527 | |||
528 | /* | ||
529 | * If needed, fragment the IEs buffer (at IE boundaries) into short | ||
530 | * enough fragments to fit into IW_GENERIC_IE_MAX octet messages. | ||
531 | */ | ||
532 | pos = bss->information_elements; | ||
533 | end = pos + bss->len_information_elements; | ||
534 | |||
535 | while (end - pos > IW_GENERIC_IE_MAX) { | ||
536 | next = pos + 2 + pos[1]; | ||
537 | while (next + 2 + next[1] - pos < IW_GENERIC_IE_MAX) | ||
538 | next = next + 2 + next[1]; | ||
539 | |||
540 | memset(&iwe, 0, sizeof(iwe)); | ||
541 | iwe.cmd = IWEVGENIE; | ||
542 | iwe.u.data.length = next - pos; | ||
543 | *current_ev = iwe_stream_add_point(info, *current_ev, | ||
544 | end_buf, &iwe, pos); | ||
545 | |||
546 | pos = next; | ||
547 | } | ||
548 | |||
549 | if (end > pos) { | ||
550 | memset(&iwe, 0, sizeof(iwe)); | ||
551 | iwe.cmd = IWEVGENIE; | ||
552 | iwe.u.data.length = end - pos; | ||
553 | *current_ev = iwe_stream_add_point(info, *current_ev, | ||
554 | end_buf, &iwe, pos); | ||
555 | } | ||
556 | } | ||
557 | |||
558 | |||
559 | static char * | ||
560 | ieee80211_bss(struct iw_request_info *info, | ||
561 | struct cfg80211_internal_bss *bss, | ||
562 | char *current_ev, char *end_buf) | ||
563 | { | ||
564 | struct iw_event iwe; | ||
565 | u8 *buf, *cfg, *p; | ||
566 | u8 *ie = bss->pub.information_elements; | ||
567 | int rem = bss->pub.len_information_elements, i; | ||
568 | bool ismesh = false; | ||
569 | |||
570 | memset(&iwe, 0, sizeof(iwe)); | ||
571 | iwe.cmd = SIOCGIWAP; | ||
572 | iwe.u.ap_addr.sa_family = ARPHRD_ETHER; | ||
573 | memcpy(iwe.u.ap_addr.sa_data, bss->pub.bssid, ETH_ALEN); | ||
574 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
575 | IW_EV_ADDR_LEN); | ||
576 | |||
577 | memset(&iwe, 0, sizeof(iwe)); | ||
578 | iwe.cmd = SIOCGIWFREQ; | ||
579 | iwe.u.freq.m = ieee80211_frequency_to_channel(bss->pub.channel->center_freq); | ||
580 | iwe.u.freq.e = 0; | ||
581 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
582 | IW_EV_FREQ_LEN); | ||
583 | |||
584 | memset(&iwe, 0, sizeof(iwe)); | ||
585 | iwe.cmd = SIOCGIWFREQ; | ||
586 | iwe.u.freq.m = bss->pub.channel->center_freq; | ||
587 | iwe.u.freq.e = 6; | ||
588 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, | ||
589 | IW_EV_FREQ_LEN); | ||
590 | |||
591 | if (bss->pub.signal_type != CFG80211_SIGNAL_TYPE_NONE) { | ||
592 | memset(&iwe, 0, sizeof(iwe)); | ||
593 | iwe.cmd = IWEVQUAL; | ||
594 | iwe.u.qual.updated = IW_QUAL_LEVEL_UPDATED | | ||
595 | IW_QUAL_NOISE_INVALID | | ||
596 | IW_QUAL_QUAL_INVALID; | ||
597 | switch (bss->pub.signal_type) { | ||
598 | case CFG80211_SIGNAL_TYPE_MBM: | ||
599 | iwe.u.qual.level = bss->pub.signal / 100; | ||
600 | iwe.u.qual.updated |= IW_QUAL_DBM; | ||
601 | break; | ||
602 | case CFG80211_SIGNAL_TYPE_UNSPEC: | ||
603 | iwe.u.qual.level = bss->pub.signal; | ||
604 | break; | ||
605 | default: | ||
606 | /* not reached */ | ||
607 | break; | ||
608 | } | ||
609 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, | ||
610 | &iwe, IW_EV_QUAL_LEN); | ||
611 | } | ||
612 | |||
613 | memset(&iwe, 0, sizeof(iwe)); | ||
614 | iwe.cmd = SIOCGIWENCODE; | ||
615 | if (bss->pub.capability & WLAN_CAPABILITY_PRIVACY) | ||
616 | iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; | ||
617 | else | ||
618 | iwe.u.data.flags = IW_ENCODE_DISABLED; | ||
619 | iwe.u.data.length = 0; | ||
620 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
621 | &iwe, ""); | ||
622 | |||
623 | while (rem >= 2) { | ||
624 | /* invalid data */ | ||
625 | if (ie[1] > rem - 2) | ||
626 | break; | ||
627 | |||
628 | switch (ie[0]) { | ||
629 | case WLAN_EID_SSID: | ||
630 | memset(&iwe, 0, sizeof(iwe)); | ||
631 | iwe.cmd = SIOCGIWESSID; | ||
632 | iwe.u.data.length = ie[1]; | ||
633 | iwe.u.data.flags = 1; | ||
634 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
635 | &iwe, ie + 2); | ||
636 | break; | ||
637 | case WLAN_EID_MESH_ID: | ||
638 | memset(&iwe, 0, sizeof(iwe)); | ||
639 | iwe.cmd = SIOCGIWESSID; | ||
640 | iwe.u.data.length = ie[1]; | ||
641 | iwe.u.data.flags = 1; | ||
642 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
643 | &iwe, ie + 2); | ||
644 | break; | ||
645 | case WLAN_EID_MESH_CONFIG: | ||
646 | ismesh = true; | ||
647 | if (ie[1] != IEEE80211_MESH_CONFIG_LEN) | ||
648 | break; | ||
649 | buf = kmalloc(50, GFP_ATOMIC); | ||
650 | if (!buf) | ||
651 | break; | ||
652 | cfg = ie + 2; | ||
653 | memset(&iwe, 0, sizeof(iwe)); | ||
654 | iwe.cmd = IWEVCUSTOM; | ||
655 | sprintf(buf, "Mesh network (version %d)", cfg[0]); | ||
656 | iwe.u.data.length = strlen(buf); | ||
657 | current_ev = iwe_stream_add_point(info, current_ev, | ||
658 | end_buf, | ||
659 | &iwe, buf); | ||
660 | sprintf(buf, "Path Selection Protocol ID: " | ||
661 | "0x%02X%02X%02X%02X", cfg[1], cfg[2], cfg[3], | ||
662 | cfg[4]); | ||
663 | iwe.u.data.length = strlen(buf); | ||
664 | current_ev = iwe_stream_add_point(info, current_ev, | ||
665 | end_buf, | ||
666 | &iwe, buf); | ||
667 | sprintf(buf, "Path Selection Metric ID: " | ||
668 | "0x%02X%02X%02X%02X", cfg[5], cfg[6], cfg[7], | ||
669 | cfg[8]); | ||
670 | iwe.u.data.length = strlen(buf); | ||
671 | current_ev = iwe_stream_add_point(info, current_ev, | ||
672 | end_buf, | ||
673 | &iwe, buf); | ||
674 | sprintf(buf, "Congestion Control Mode ID: " | ||
675 | "0x%02X%02X%02X%02X", cfg[9], cfg[10], | ||
676 | cfg[11], cfg[12]); | ||
677 | iwe.u.data.length = strlen(buf); | ||
678 | current_ev = iwe_stream_add_point(info, current_ev, | ||
679 | end_buf, | ||
680 | &iwe, buf); | ||
681 | sprintf(buf, "Channel Precedence: " | ||
682 | "0x%02X%02X%02X%02X", cfg[13], cfg[14], | ||
683 | cfg[15], cfg[16]); | ||
684 | iwe.u.data.length = strlen(buf); | ||
685 | current_ev = iwe_stream_add_point(info, current_ev, | ||
686 | end_buf, | ||
687 | &iwe, buf); | ||
688 | kfree(buf); | ||
689 | break; | ||
690 | case WLAN_EID_SUPP_RATES: | ||
691 | case WLAN_EID_EXT_SUPP_RATES: | ||
692 | /* display all supported rates in readable format */ | ||
693 | p = current_ev + iwe_stream_lcp_len(info); | ||
694 | |||
695 | memset(&iwe, 0, sizeof(iwe)); | ||
696 | iwe.cmd = SIOCGIWRATE; | ||
697 | /* Those two flags are ignored... */ | ||
698 | iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; | ||
699 | |||
700 | for (i = 0; i < ie[1]; i++) { | ||
701 | iwe.u.bitrate.value = | ||
702 | ((ie[i + 2] & 0x7f) * 500000); | ||
703 | p = iwe_stream_add_value(info, current_ev, p, | ||
704 | end_buf, &iwe, IW_EV_PARAM_LEN); | ||
705 | } | ||
706 | current_ev = p; | ||
707 | break; | ||
708 | } | ||
709 | rem -= ie[1] + 2; | ||
710 | ie += ie[1] + 2; | ||
711 | } | ||
712 | |||
713 | if (bss->pub.capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS) | ||
714 | || ismesh) { | ||
715 | memset(&iwe, 0, sizeof(iwe)); | ||
716 | iwe.cmd = SIOCGIWMODE; | ||
717 | if (ismesh) | ||
718 | iwe.u.mode = IW_MODE_MESH; | ||
719 | else if (bss->pub.capability & WLAN_CAPABILITY_ESS) | ||
720 | iwe.u.mode = IW_MODE_MASTER; | ||
721 | else | ||
722 | iwe.u.mode = IW_MODE_ADHOC; | ||
723 | current_ev = iwe_stream_add_event(info, current_ev, end_buf, | ||
724 | &iwe, IW_EV_UINT_LEN); | ||
725 | } | ||
726 | |||
727 | buf = kmalloc(30, GFP_ATOMIC); | ||
728 | if (buf) { | ||
729 | memset(&iwe, 0, sizeof(iwe)); | ||
730 | iwe.cmd = IWEVCUSTOM; | ||
731 | sprintf(buf, "tsf=%016llx", (unsigned long long)(bss->pub.tsf)); | ||
732 | iwe.u.data.length = strlen(buf); | ||
733 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | ||
734 | &iwe, buf); | ||
735 | memset(&iwe, 0, sizeof(iwe)); | ||
736 | iwe.cmd = IWEVCUSTOM; | ||
737 | sprintf(buf, " Last beacon: %dms ago", | ||
738 | jiffies_to_msecs(jiffies - bss->ts)); | ||
739 | iwe.u.data.length = strlen(buf); | ||
740 | current_ev = iwe_stream_add_point(info, current_ev, | ||
741 | end_buf, &iwe, buf); | ||
742 | kfree(buf); | ||
743 | } | ||
744 | |||
745 | ieee80211_scan_add_ies(info, &bss->pub, ¤t_ev, end_buf); | ||
746 | |||
747 | return current_ev; | ||
748 | } | ||
749 | |||
750 | |||
751 | static int ieee80211_scan_results(struct cfg80211_registered_device *dev, | ||
752 | struct iw_request_info *info, | ||
753 | char *buf, size_t len) | ||
754 | { | ||
755 | char *current_ev = buf; | ||
756 | char *end_buf = buf + len; | ||
757 | struct cfg80211_internal_bss *bss; | ||
758 | |||
759 | spin_lock_bh(&dev->bss_lock); | ||
760 | cfg80211_bss_expire(dev); | ||
761 | |||
762 | list_for_each_entry(bss, &dev->bss_list, list) { | ||
763 | if (buf + len - current_ev <= IW_EV_ADDR_LEN) { | ||
764 | spin_unlock_bh(&dev->bss_lock); | ||
765 | return -E2BIG; | ||
766 | } | ||
767 | current_ev = ieee80211_bss(info, bss, | ||
768 | current_ev, end_buf); | ||
769 | } | ||
770 | spin_unlock_bh(&dev->bss_lock); | ||
771 | return current_ev - buf; | ||
772 | } | ||
773 | |||
774 | |||
775 | int cfg80211_wext_giwscan(struct net_device *dev, | ||
776 | struct iw_request_info *info, | ||
777 | struct iw_point *data, char *extra) | ||
778 | { | ||
779 | struct cfg80211_registered_device *rdev; | ||
780 | int res; | ||
781 | |||
782 | if (!netif_running(dev)) | ||
783 | return -ENETDOWN; | ||
784 | |||
785 | rdev = cfg80211_get_dev_from_ifindex(dev->ifindex); | ||
786 | |||
787 | if (IS_ERR(rdev)) | ||
788 | return PTR_ERR(rdev); | ||
789 | |||
790 | if (rdev->scan_req) { | ||
791 | res = -EAGAIN; | ||
792 | goto out; | ||
793 | } | ||
794 | |||
795 | res = ieee80211_scan_results(rdev, info, extra, data->length); | ||
796 | data->length = 0; | ||
797 | if (res >= 0) { | ||
798 | data->length = res; | ||
799 | res = 0; | ||
800 | } | ||
801 | |||
802 | out: | ||
803 | cfg80211_put_dev(rdev); | ||
804 | return res; | ||
805 | } | ||
806 | EXPORT_SYMBOL(cfg80211_wext_giwscan); | ||
807 | #endif | ||