2008-09-08 19:44:25 +04:00
/*
2008-09-08 19:44:27 +04:00
* Scanning implementation
*
2008-09-08 19:44:25 +04:00
* Copyright 2003 , Jouni Malinen < jkmaline @ cc . hut . fi >
* Copyright 2004 , Instant802 Networks , Inc .
* Copyright 2005 , Devicescape Software , Inc .
* Copyright 2006 - 2007 Jiri Benc < jbenc @ suse . cz >
* Copyright 2007 , Michael Wu < flamingice @ sourmilk . net >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
2008-09-08 19:44:27 +04:00
/* TODO:
* order BSS list by RSSI ( ? ) ( " quality of AP " )
* scan result table filtering ( by capability ( privacy , IBSS / BSS , WPA / RSN IE ,
* SSID )
*/
2008-09-08 19:44:25 +04:00
# include <linux/wireless.h>
# include <linux/if_arp.h>
# include <net/mac80211.h>
# include <net/iw_handler.h>
# include "ieee80211_i.h"
2008-09-08 19:44:27 +04:00
# include "mesh.h"
2008-09-08 19:44:25 +04:00
# define IEEE80211_PROBE_DELAY (HZ / 33)
# define IEEE80211_CHANNEL_TIME (HZ / 33)
# define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 5)
2008-09-08 19:44:27 +04:00
void ieee80211_rx_bss_list_init ( struct ieee80211_local * local )
{
2008-09-11 02:01:55 +04:00
spin_lock_init ( & local - > bss_lock ) ;
INIT_LIST_HEAD ( & local - > bss_list ) ;
2008-09-08 19:44:27 +04:00
}
void ieee80211_rx_bss_list_deinit ( struct ieee80211_local * local )
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss , * tmp ;
2008-09-08 19:44:27 +04:00
2008-09-11 02:01:55 +04:00
list_for_each_entry_safe ( bss , tmp , & local - > bss_list , list )
2008-09-08 19:44:27 +04:00
ieee80211_rx_bss_put ( local , bss ) ;
}
2008-09-11 02:01:55 +04:00
struct ieee80211_bss *
2008-09-08 19:44:27 +04:00
ieee80211_rx_bss_get ( struct ieee80211_local * local , u8 * bssid , int freq ,
u8 * ssid , u8 ssid_len )
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:27 +04:00
2008-09-11 02:01:55 +04:00
spin_lock_bh ( & local - > bss_lock ) ;
bss = local - > bss_hash [ STA_HASH ( bssid ) ] ;
2008-09-08 19:44:27 +04:00
while ( bss ) {
if ( ! bss_mesh_cfg ( bss ) & &
! memcmp ( bss - > bssid , bssid , ETH_ALEN ) & &
bss - > freq = = freq & &
bss - > ssid_len = = ssid_len & &
( ssid_len = = 0 | | ! memcmp ( bss - > ssid , ssid , ssid_len ) ) ) {
atomic_inc ( & bss - > users ) ;
break ;
}
bss = bss - > hnext ;
}
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
return bss ;
}
2008-09-11 02:01:55 +04:00
/* Caller must hold local->bss_lock */
2008-09-08 19:44:27 +04:00
static void __ieee80211_rx_bss_hash_add ( struct ieee80211_local * local ,
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss )
2008-09-08 19:44:27 +04:00
{
u8 hash_idx ;
if ( bss_mesh_cfg ( bss ) )
hash_idx = mesh_id_hash ( bss_mesh_id ( bss ) ,
bss_mesh_id_len ( bss ) ) ;
else
hash_idx = STA_HASH ( bss - > bssid ) ;
2008-09-11 02:01:55 +04:00
bss - > hnext = local - > bss_hash [ hash_idx ] ;
local - > bss_hash [ hash_idx ] = bss ;
2008-09-08 19:44:27 +04:00
}
2008-09-11 02:01:55 +04:00
/* Caller must hold local->bss_lock */
2008-09-08 19:44:27 +04:00
static void __ieee80211_rx_bss_hash_del ( struct ieee80211_local * local ,
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss )
2008-09-08 19:44:27 +04:00
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * b , * prev = NULL ;
b = local - > bss_hash [ STA_HASH ( bss - > bssid ) ] ;
2008-09-08 19:44:27 +04:00
while ( b ) {
if ( b = = bss ) {
if ( ! prev )
2008-09-11 02:01:55 +04:00
local - > bss_hash [ STA_HASH ( bss - > bssid ) ] =
2008-09-08 19:44:27 +04:00
bss - > hnext ;
else
prev - > hnext = bss - > hnext ;
break ;
}
prev = b ;
b = b - > hnext ;
}
}
2008-09-11 02:01:55 +04:00
struct ieee80211_bss *
2008-09-08 19:44:27 +04:00
ieee80211_rx_bss_add ( struct ieee80211_local * local , u8 * bssid , int freq ,
u8 * ssid , u8 ssid_len )
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:27 +04:00
bss = kzalloc ( sizeof ( * bss ) , GFP_ATOMIC ) ;
if ( ! bss )
return NULL ;
atomic_set ( & bss - > users , 2 ) ;
memcpy ( bss - > bssid , bssid , ETH_ALEN ) ;
bss - > freq = freq ;
if ( ssid & & ssid_len < = IEEE80211_MAX_SSID_LEN ) {
memcpy ( bss - > ssid , ssid , ssid_len ) ;
bss - > ssid_len = ssid_len ;
}
2008-09-11 02:01:55 +04:00
spin_lock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
/* TODO: order by RSSI? */
2008-09-11 02:01:55 +04:00
list_add_tail ( & bss - > list , & local - > bss_list ) ;
2008-09-08 19:44:27 +04:00
__ieee80211_rx_bss_hash_add ( local , bss ) ;
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
return bss ;
}
# ifdef CONFIG_MAC80211_MESH
2008-09-11 02:01:55 +04:00
static struct ieee80211_bss *
2008-09-08 19:44:27 +04:00
ieee80211_rx_mesh_bss_get ( struct ieee80211_local * local , u8 * mesh_id , int mesh_id_len ,
u8 * mesh_cfg , int freq )
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:27 +04:00
2008-09-11 02:01:55 +04:00
spin_lock_bh ( & local - > bss_lock ) ;
bss = local - > bss_hash [ mesh_id_hash ( mesh_id , mesh_id_len ) ] ;
2008-09-08 19:44:27 +04:00
while ( bss ) {
if ( bss_mesh_cfg ( bss ) & &
! memcmp ( bss_mesh_cfg ( bss ) , mesh_cfg , MESH_CFG_CMP_LEN ) & &
bss - > freq = = freq & &
mesh_id_len = = bss - > mesh_id_len & &
( mesh_id_len = = 0 | | ! memcmp ( bss - > mesh_id , mesh_id ,
mesh_id_len ) ) ) {
atomic_inc ( & bss - > users ) ;
break ;
}
bss = bss - > hnext ;
}
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
return bss ;
}
2008-09-11 02:01:55 +04:00
static struct ieee80211_bss *
2008-09-08 19:44:27 +04:00
ieee80211_rx_mesh_bss_add ( struct ieee80211_local * local , u8 * mesh_id , int mesh_id_len ,
u8 * mesh_cfg , int mesh_config_len , int freq )
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:27 +04:00
2008-10-28 13:12:57 +03:00
if ( mesh_config_len ! = IEEE80211_MESH_CONFIG_LEN )
2008-09-08 19:44:27 +04:00
return NULL ;
bss = kzalloc ( sizeof ( * bss ) , GFP_ATOMIC ) ;
if ( ! bss )
return NULL ;
bss - > mesh_cfg = kmalloc ( MESH_CFG_CMP_LEN , GFP_ATOMIC ) ;
if ( ! bss - > mesh_cfg ) {
kfree ( bss ) ;
return NULL ;
}
if ( mesh_id_len & & mesh_id_len < = IEEE80211_MAX_MESH_ID_LEN ) {
bss - > mesh_id = kmalloc ( mesh_id_len , GFP_ATOMIC ) ;
if ( ! bss - > mesh_id ) {
kfree ( bss - > mesh_cfg ) ;
kfree ( bss ) ;
return NULL ;
}
memcpy ( bss - > mesh_id , mesh_id , mesh_id_len ) ;
}
atomic_set ( & bss - > users , 2 ) ;
memcpy ( bss - > mesh_cfg , mesh_cfg , MESH_CFG_CMP_LEN ) ;
bss - > mesh_id_len = mesh_id_len ;
bss - > freq = freq ;
2008-09-11 02:01:55 +04:00
spin_lock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
/* TODO: order by RSSI? */
2008-09-11 02:01:55 +04:00
list_add_tail ( & bss - > list , & local - > bss_list ) ;
2008-09-08 19:44:27 +04:00
__ieee80211_rx_bss_hash_add ( local , bss ) ;
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
return bss ;
}
# endif
2008-09-11 02:01:55 +04:00
static void ieee80211_rx_bss_free ( struct ieee80211_bss * bss )
2008-09-08 19:44:27 +04:00
{
kfree ( bss - > ies ) ;
kfree ( bss_mesh_id ( bss ) ) ;
kfree ( bss_mesh_cfg ( bss ) ) ;
kfree ( bss ) ;
}
void ieee80211_rx_bss_put ( struct ieee80211_local * local ,
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss )
2008-09-08 19:44:27 +04:00
{
local_bh_disable ( ) ;
2008-09-11 02:01:55 +04:00
if ( ! atomic_dec_and_lock ( & bss - > users , & local - > bss_lock ) ) {
2008-09-08 19:44:27 +04:00
local_bh_enable ( ) ;
return ;
}
__ieee80211_rx_bss_hash_del ( local , bss ) ;
list_del ( & bss - > list ) ;
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
ieee80211_rx_bss_free ( bss ) ;
}
2008-09-11 02:01:55 +04:00
struct ieee80211_bss *
2008-09-08 19:44:27 +04:00
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 )
{
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:27 +04:00
int clen ;
# ifdef CONFIG_MAC80211_MESH
if ( elems - > mesh_config )
bss = ieee80211_rx_mesh_bss_get ( local , elems - > mesh_id ,
elems - > mesh_id_len , elems - > mesh_config , freq ) ;
else
# endif
bss = ieee80211_rx_bss_get ( local , mgmt - > bssid , freq ,
elems - > ssid , elems - > ssid_len ) ;
if ( ! bss ) {
# ifdef CONFIG_MAC80211_MESH
if ( elems - > mesh_config )
bss = ieee80211_rx_mesh_bss_add ( local , elems - > mesh_id ,
elems - > mesh_id_len , elems - > mesh_config ,
elems - > mesh_config_len , freq ) ;
else
# endif
bss = ieee80211_rx_bss_add ( local , mgmt - > bssid , freq ,
elems - > ssid , elems - > ssid_len ) ;
if ( ! bss )
return NULL ;
} else {
#if 0
/* TODO: order by RSSI? */
2008-09-11 02:01:55 +04:00
spin_lock_bh ( & local - > bss_lock ) ;
list_move_tail ( & bss - > list , & local - > bss_list ) ;
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:27 +04:00
# endif
}
/* save the ERP value so that it is available at association time */
if ( elems - > erp_info & & elems - > erp_info_len > = 1 ) {
bss - > erp_value = elems - > erp_info [ 0 ] ;
bss - > has_erp_value = 1 ;
}
bss - > beacon_int = le16_to_cpu ( mgmt - > u . beacon . beacon_int ) ;
bss - > capability = le16_to_cpu ( mgmt - > u . beacon . capab_info ) ;
if ( elems - > tim ) {
struct ieee80211_tim_ie * tim_ie =
( struct ieee80211_tim_ie * ) elems - > tim ;
bss - > dtim_period = tim_ie - > dtim_period ;
}
/* set default value for buggy APs */
if ( ! elems - > tim | | bss - > dtim_period = = 0 )
bss - > dtim_period = 1 ;
bss - > supp_rates_len = 0 ;
if ( elems - > supp_rates ) {
clen = IEEE80211_MAX_SUPP_RATES - bss - > supp_rates_len ;
if ( clen > elems - > supp_rates_len )
clen = elems - > supp_rates_len ;
memcpy ( & bss - > supp_rates [ bss - > supp_rates_len ] , elems - > supp_rates ,
clen ) ;
bss - > supp_rates_len + = clen ;
}
if ( elems - > ext_supp_rates ) {
clen = IEEE80211_MAX_SUPP_RATES - bss - > supp_rates_len ;
if ( clen > elems - > ext_supp_rates_len )
clen = elems - > ext_supp_rates_len ;
memcpy ( & bss - > supp_rates [ bss - > supp_rates_len ] ,
elems - > ext_supp_rates , clen ) ;
bss - > supp_rates_len + = clen ;
}
bss - > band = rx_status - > band ;
bss - > timestamp = le64_to_cpu ( mgmt - > u . beacon . timestamp ) ;
bss - > last_update = jiffies ;
bss - > signal = rx_status - > signal ;
bss - > noise = rx_status - > noise ;
bss - > qual = rx_status - > qual ;
bss - > wmm_used = elems - > wmm_param | | elems - > wmm_info ;
if ( ! beacon )
bss - > last_probe_resp = jiffies ;
/*
* For probe responses , or if we don ' t have any information yet ,
* use the IEs from the beacon .
*/
if ( ! bss - > ies | | ! beacon ) {
if ( bss - > ies = = NULL | | bss - > ies_len < elems - > total_len ) {
kfree ( bss - > ies ) ;
bss - > ies = kmalloc ( elems - > total_len , GFP_ATOMIC ) ;
}
if ( bss - > ies ) {
memcpy ( bss - > ies , elems - > ie_start , elems - > total_len ) ;
bss - > ies_len = elems - > total_len ;
} else
bss - > ies_len = 0 ;
}
return bss ;
}
2008-09-08 19:44:25 +04:00
2008-09-08 19:44:26 +04:00
ieee80211_rx_result
2008-09-11 02:01:55 +04:00
ieee80211_scan_rx ( struct ieee80211_sub_if_data * sdata , struct sk_buff * skb ,
struct ieee80211_rx_status * rx_status )
2008-09-08 19:44:26 +04:00
{
struct ieee80211_mgmt * mgmt ;
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:26 +04:00
u8 * elements ;
struct ieee80211_channel * channel ;
size_t baselen ;
int freq ;
__le16 fc ;
bool presp , beacon = false ;
struct ieee802_11_elems elems ;
if ( skb - > len < 2 )
return RX_DROP_UNUSABLE ;
mgmt = ( struct ieee80211_mgmt * ) skb - > data ;
fc = mgmt - > frame_control ;
if ( ieee80211_is_ctl ( fc ) )
return RX_CONTINUE ;
if ( skb - > len < 24 )
return RX_DROP_MONITOR ;
presp = ieee80211_is_probe_resp ( fc ) ;
if ( presp ) {
/* ignore ProbeResp to foreign address */
if ( memcmp ( mgmt - > da , sdata - > dev - > dev_addr , ETH_ALEN ) )
return RX_DROP_MONITOR ;
presp = true ;
elements = mgmt - > u . probe_resp . variable ;
baselen = offsetof ( struct ieee80211_mgmt , u . probe_resp . variable ) ;
} else {
beacon = ieee80211_is_beacon ( fc ) ;
baselen = offsetof ( struct ieee80211_mgmt , u . beacon . variable ) ;
elements = mgmt - > u . beacon . variable ;
}
if ( ! presp & & ! beacon )
return RX_CONTINUE ;
if ( baselen > skb - > len )
return RX_DROP_MONITOR ;
ieee802_11_parse_elems ( elements , skb - > len - baselen , & elems ) ;
if ( elems . ds_params & & elems . ds_params_len = = 1 )
freq = ieee80211_channel_to_frequency ( elems . ds_params [ 0 ] ) ;
else
freq = rx_status - > freq ;
channel = ieee80211_get_channel ( sdata - > local - > hw . wiphy , freq ) ;
if ( ! channel | | channel - > flags & IEEE80211_CHAN_DISABLED )
return RX_DROP_MONITOR ;
bss = ieee80211_bss_info_update ( sdata - > local , rx_status ,
mgmt , skb - > len , & elems ,
freq , beacon ) ;
2008-10-11 04:29:55 +04:00
if ( bss )
ieee80211_rx_bss_put ( sdata - > local , bss ) ;
2008-09-08 19:44:26 +04:00
dev_kfree_skb ( skb ) ;
return RX_QUEUED ;
}
2008-09-08 19:44:25 +04:00
static void ieee80211_send_nullfunc ( struct ieee80211_local * local ,
struct ieee80211_sub_if_data * sdata ,
int powersave )
{
struct sk_buff * skb ;
struct ieee80211_hdr * nullfunc ;
__le16 fc ;
skb = dev_alloc_skb ( local - > hw . extra_tx_headroom + 24 ) ;
if ( ! skb ) {
printk ( KERN_DEBUG " %s: failed to allocate buffer for nullfunc "
" frame \n " , sdata - > dev - > name ) ;
return ;
}
skb_reserve ( skb , local - > hw . extra_tx_headroom ) ;
nullfunc = ( struct ieee80211_hdr * ) skb_put ( skb , 24 ) ;
memset ( nullfunc , 0 , 24 ) ;
fc = cpu_to_le16 ( IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC |
IEEE80211_FCTL_TODS ) ;
if ( powersave )
fc | = cpu_to_le16 ( IEEE80211_FCTL_PM ) ;
nullfunc - > frame_control = fc ;
memcpy ( nullfunc - > addr1 , sdata - > u . sta . bssid , ETH_ALEN ) ;
memcpy ( nullfunc - > addr2 , sdata - > dev - > dev_addr , ETH_ALEN ) ;
memcpy ( nullfunc - > addr3 , sdata - > u . sta . bssid , ETH_ALEN ) ;
2008-09-09 17:07:09 +04:00
ieee80211_tx_skb ( sdata , skb , 0 ) ;
2008-09-08 19:44:25 +04:00
}
void ieee80211_scan_completed ( struct ieee80211_hw * hw )
{
struct ieee80211_local * local = hw_to_local ( hw ) ;
struct ieee80211_sub_if_data * sdata ;
union iwreq_data wrqu ;
2008-09-11 02:01:55 +04:00
if ( WARN_ON ( ! local - > hw_scanning & & ! local - > sw_scanning ) )
2008-09-11 02:01:51 +04:00
return ;
2008-09-08 19:44:25 +04:00
local - > last_scan_completed = jiffies ;
memset ( & wrqu , 0 , sizeof ( wrqu ) ) ;
2008-09-11 02:01:51 +04:00
/*
* 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 ) ;
2008-09-08 19:44:25 +04:00
2008-09-11 02:01:55 +04:00
if ( local - > hw_scanning ) {
local - > hw_scanning = false ;
2008-10-09 14:18:51 +04:00
/*
* Somebody might have requested channel change during scan
* that we won ' t have acted upon , try now . ieee80211_hw_config
* will set the flag based on actual changes .
*/
ieee80211_hw_config ( local , 0 ) ;
2008-09-08 19:44:25 +04:00
goto done ;
}
2008-09-11 02:01:55 +04:00
local - > sw_scanning = false ;
2008-10-09 14:18:51 +04:00
ieee80211_hw_config ( local , IEEE80211_CONF_CHANGE_CHANNEL ) ;
2008-09-08 19:44:25 +04:00
netif_tx_lock_bh ( local - > mdev ) ;
netif_addr_lock ( local - > mdev ) ;
local - > filter_flags & = ~ FIF_BCN_PRBRESP_PROMISC ;
local - > ops - > configure_filter ( local_to_hw ( local ) ,
FIF_BCN_PRBRESP_PROMISC ,
& local - > filter_flags ,
local - > mdev - > mc_count ,
local - > mdev - > mc_list ) ;
netif_addr_unlock ( local - > mdev ) ;
netif_tx_unlock_bh ( local - > mdev ) ;
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( sdata , & local - > interfaces , list ) {
/* Tell AP we're back */
2008-09-11 02:01:58 +04:00
if ( sdata - > vif . type = = NL80211_IFTYPE_STATION ) {
2008-09-08 19:44:25 +04:00
if ( sdata - > u . sta . flags & IEEE80211_STA_ASSOCIATED ) {
ieee80211_send_nullfunc ( local , sdata , 0 ) ;
netif_tx_wake_all_queues ( sdata - > dev ) ;
}
} else
netif_tx_wake_all_queues ( sdata - > dev ) ;
}
rcu_read_unlock ( ) ;
done :
ieee80211_mlme_notify_scan_completed ( local ) ;
2008-09-11 02:01:49 +04:00
ieee80211_mesh_notify_scan_completed ( local ) ;
2008-09-08 19:44:25 +04:00
}
EXPORT_SYMBOL ( ieee80211_scan_completed ) ;
2008-09-11 02:01:55 +04:00
void ieee80211_scan_work ( struct work_struct * work )
2008-09-08 19:44:25 +04:00
{
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 ;
unsigned long next_delay = 0 ;
2008-09-11 02:01:51 +04:00
/*
* Avoid re - scheduling when the sdata is going away .
*/
if ( ! netif_running ( sdata - > dev ) )
2008-09-08 19:44:25 +04:00
return ;
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 ) ) ;
return ;
}
skip = 0 ;
chan = & sband - > channels [ local - > scan_channel_idx ] ;
if ( chan - > flags & IEEE80211_CHAN_DISABLED | |
2008-09-11 02:01:58 +04:00
( sdata - > vif . type = = NL80211_IFTYPE_ADHOC & &
2008-09-08 19:44:25 +04:00
chan - > flags & IEEE80211_CHAN_NO_IBSS ) )
skip = 1 ;
if ( ! skip ) {
local - > scan_channel = chan ;
2008-10-09 14:18:51 +04:00
if ( ieee80211_hw_config ( local ,
IEEE80211_CONF_CHANGE_CHANNEL ) )
2008-09-08 19:44:25 +04:00
skip = 1 ;
}
/* 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 ;
next_delay = IEEE80211_PROBE_DELAY +
usecs_to_jiffies ( local - > hw . channel_change_time ) ;
local - > scan_state = SCAN_SEND_PROBE ;
break ;
case SCAN_SEND_PROBE :
next_delay = IEEE80211_PASSIVE_CHANNEL_TIME ;
local - > scan_state = SCAN_SET_CHANNEL ;
if ( local - > scan_channel - > flags & IEEE80211_CHAN_PASSIVE_SCAN )
break ;
ieee80211_send_probe_req ( sdata , NULL , local - > scan_ssid ,
local - > scan_ssid_len ) ;
next_delay = IEEE80211_CHANNEL_TIME ;
break ;
}
2008-09-11 02:01:51 +04:00
queue_delayed_work ( local - > hw . workqueue , & local - > scan_work ,
next_delay ) ;
2008-09-08 19:44:25 +04:00
}
2008-09-11 02:01:55 +04:00
int ieee80211_start_scan ( struct ieee80211_sub_if_data * scan_sdata ,
u8 * ssid , size_t ssid_len )
2008-09-08 19:44:25 +04:00
{
struct ieee80211_local * local = scan_sdata - > local ;
struct ieee80211_sub_if_data * sdata ;
if ( ssid_len > IEEE80211_MAX_SSID_LEN )
return - EINVAL ;
/* MLME-SCAN.request (page 118) page 144 (11.1.3.1)
* BSSType : INFRASTRUCTURE , INDEPENDENT , ANY_BSS
* BSSID : MACAddress
* SSID
* ScanType : ACTIVE , PASSIVE
* ProbeDelay : delay ( in microseconds ) to be used prior to transmitting
* a Probe frame during active scanning
* ChannelList
* MinChannelTime ( > = ProbeDelay ) , in TU
* MaxChannelTime : ( > = MinChannelTime ) , in TU
*/
/* MLME-SCAN.confirm
* BSSDescriptionSet
* ResultCode : SUCCESS , INVALID_PARAMETERS
*/
2008-09-11 02:01:55 +04:00
if ( local - > sw_scanning | | local - > hw_scanning ) {
2008-09-08 19:44:25 +04:00
if ( local - > scan_sdata = = scan_sdata )
return 0 ;
return - EBUSY ;
}
if ( local - > ops - > hw_scan ) {
2008-09-11 02:01:51 +04:00
int rc ;
2008-09-11 02:01:55 +04:00
local - > hw_scanning = true ;
2008-09-11 02:01:51 +04:00
rc = local - > ops - > hw_scan ( local_to_hw ( local ) , ssid , ssid_len ) ;
if ( rc ) {
2008-09-11 02:01:55 +04:00
local - > hw_scanning = false ;
2008-09-11 02:01:51 +04:00
return rc ;
2008-09-08 19:44:25 +04:00
}
2008-09-11 02:01:51 +04:00
local - > scan_sdata = scan_sdata ;
return 0 ;
2008-09-08 19:44:25 +04:00
}
2008-09-11 02:01:55 +04:00
local - > sw_scanning = true ;
2008-09-08 19:44:25 +04:00
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( sdata , & local - > interfaces , list ) {
2008-09-11 02:01:58 +04:00
if ( sdata - > vif . type = = NL80211_IFTYPE_STATION ) {
2008-09-08 19:44:25 +04:00
if ( sdata - > u . sta . flags & IEEE80211_STA_ASSOCIATED ) {
netif_tx_stop_all_queues ( sdata - > dev ) ;
ieee80211_send_nullfunc ( local , sdata , 1 ) ;
}
} else
netif_tx_stop_all_queues ( sdata - > dev ) ;
}
rcu_read_unlock ( ) ;
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 ;
netif_addr_lock_bh ( local - > mdev ) ;
local - > filter_flags | = FIF_BCN_PRBRESP_PROMISC ;
local - > ops - > configure_filter ( local_to_hw ( local ) ,
FIF_BCN_PRBRESP_PROMISC ,
& local - > filter_flags ,
local - > mdev - > mc_count ,
local - > mdev - > mc_list ) ;
netif_addr_unlock_bh ( local - > mdev ) ;
/* TODO: start scan as soon as all nullfunc frames are ACKed */
queue_delayed_work ( local - > hw . workqueue , & local - > scan_work ,
IEEE80211_CHANNEL_TIME ) ;
return 0 ;
}
2008-09-11 02:01:55 +04:00
int ieee80211_request_scan ( struct ieee80211_sub_if_data * sdata ,
u8 * ssid , size_t ssid_len )
2008-09-08 19:44:25 +04:00
{
struct ieee80211_local * local = sdata - > local ;
2008-09-08 19:47:23 +04:00
struct ieee80211_if_sta * ifsta ;
2008-09-08 19:44:25 +04:00
2008-09-11 02:01:58 +04:00
if ( sdata - > vif . type ! = NL80211_IFTYPE_STATION )
2008-09-11 02:01:55 +04:00
return ieee80211_start_scan ( sdata , ssid , ssid_len ) ;
2008-09-08 19:44:25 +04:00
2008-09-08 19:47:23 +04:00
/*
* STA has a state machine that might need to defer scanning
* while it ' s trying to associate / authenticate , therefore we
* queue it up to the state machine in that case .
*/
2008-09-11 02:01:55 +04:00
if ( local - > sw_scanning | | local - > hw_scanning ) {
2008-09-08 19:44:25 +04:00
if ( local - > scan_sdata = = sdata )
return 0 ;
return - EBUSY ;
}
2008-09-08 19:47:23 +04:00
ifsta = & sdata - > u . sta ;
2008-09-08 19:44:25 +04:00
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 ) ;
2008-09-08 19:47:23 +04:00
2008-09-08 19:44:25 +04:00
return 0 ;
}
2008-09-11 02:01:55 +04:00
static void ieee80211_scan_add_ies ( struct iw_request_info * info ,
struct ieee80211_bss * bss ,
char * * current_ev , char * end_buf )
2008-09-08 19:44:25 +04:00
{
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 *
2008-09-11 02:01:55 +04:00
ieee80211_scan_result ( struct ieee80211_local * local ,
struct iw_request_info * info ,
struct ieee80211_bss * bss ,
char * current_ev , char * end_buf )
2008-09-08 19:44:25 +04:00
{
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 , " " ) ;
2008-09-11 02:01:55 +04:00
ieee80211_scan_add_ies ( info , bss , & current_ev , end_buf ) ;
2008-09-08 19:44:25 +04:00
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 ;
}
2008-09-11 02:01:55 +04:00
int ieee80211_scan_results ( struct ieee80211_local * local ,
struct iw_request_info * info ,
char * buf , size_t len )
2008-09-08 19:44:25 +04:00
{
char * current_ev = buf ;
char * end_buf = buf + len ;
2008-09-11 02:01:55 +04:00
struct ieee80211_bss * bss ;
2008-09-08 19:44:25 +04:00
2008-09-11 02:01:55 +04:00
spin_lock_bh ( & local - > bss_lock ) ;
list_for_each_entry ( bss , & local - > bss_list , list ) {
2008-09-08 19:44:25 +04:00
if ( buf + len - current_ev < = IW_EV_ADDR_LEN ) {
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:25 +04:00
return - E2BIG ;
}
2008-09-11 02:01:55 +04:00
current_ev = ieee80211_scan_result ( local , info , bss ,
2008-09-08 19:44:25 +04:00
current_ev , end_buf ) ;
}
2008-09-11 02:01:55 +04:00
spin_unlock_bh ( & local - > bss_lock ) ;
2008-09-08 19:44:25 +04:00
return current_ev - buf ;
}