2009-02-10 23:25:55 +03:00
/*
* cfg80211 scan result handling
*
* Copyright 2008 Johannes Berg < johannes @ sipsolutions . net >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/netdevice.h>
# include <linux/wireless.h>
# include <linux/nl80211.h>
# include <linux/etherdevice.h>
# include <net/arp.h>
# include <net/cfg80211.h>
# include <net/iw_handler.h>
# include "core.h"
# include "nl80211.h"
2009-07-27 14:01:53 +04:00
# include "wext-compat.h"
2009-02-10 23:25:55 +03:00
2009-07-23 14:14:29 +04:00
# define IEEE80211_SCAN_RESULT_EXPIRE (15 * HZ)
2009-02-10 23:25:55 +03:00
2009-08-20 23:36:16 +04:00
void ___cfg80211_scan_done ( struct cfg80211_registered_device * rdev , bool leak )
2009-02-10 23:25:55 +03:00
{
2009-07-07 05:56:11 +04:00
struct cfg80211_scan_request * request ;
2009-02-10 23:25:55 +03:00
struct net_device * dev ;
# ifdef CONFIG_WIRELESS_EXT
union iwreq_data wrqu ;
# endif
2009-08-20 23:36:16 +04:00
ASSERT_RDEV_LOCK ( rdev ) ;
2009-07-07 05:56:11 +04:00
request = rdev - > scan_req ;
2009-08-20 23:36:16 +04:00
if ( ! request )
return ;
2009-07-14 02:33:35 +04:00
dev = request - > dev ;
2009-02-10 23:25:55 +03:00
2009-07-02 11:13:27 +04:00
/*
* This must be before sending the other events !
* Otherwise , wpa_supplicant gets completely confused with
* wext events .
*/
cfg80211_sme_scan_done ( dev ) ;
2009-07-07 05:56:11 +04:00
if ( request - > aborted )
2009-08-13 00:21:21 +04:00
nl80211_send_scan_aborted ( rdev , dev ) ;
2009-02-10 23:25:55 +03:00
else
2009-08-13 00:21:21 +04:00
nl80211_send_scan_done ( rdev , dev ) ;
2009-02-10 23:25:55 +03:00
# ifdef CONFIG_WIRELESS_EXT
2009-07-07 05:56:11 +04:00
if ( ! request - > aborted ) {
2009-02-10 23:25:55 +03:00
memset ( & wrqu , 0 , sizeof ( wrqu ) ) ;
wireless_send_event ( dev , SIOCGIWSCAN , & wrqu , NULL ) ;
}
# endif
dev_put ( dev ) ;
2009-08-13 00:21:21 +04:00
rdev - > scan_req = NULL ;
2009-08-20 23:36:16 +04:00
/*
* OK . If this is invoked with " leak " then we can ' t
* free this . . . but we ' ve cleaned it up anyway . The
* driver failed to call the scan_done callback , so
* all bets are off , it might still be trying to use
* the scan request or not . . . if it accesses the dev
* in there ( it shouldn ' t anyway ) then it may crash .
*/
if ( ! leak )
kfree ( request ) ;
2009-02-10 23:25:55 +03:00
}
2009-07-07 05:56:11 +04:00
2009-08-13 00:21:21 +04:00
void __cfg80211_scan_done ( struct work_struct * wk )
{
struct cfg80211_registered_device * rdev ;
rdev = container_of ( wk , struct cfg80211_registered_device ,
scan_done_wk ) ;
cfg80211_lock_rdev ( rdev ) ;
2009-08-20 23:36:16 +04:00
___cfg80211_scan_done ( rdev , false ) ;
2009-08-13 00:21:21 +04:00
cfg80211_unlock_rdev ( rdev ) ;
}
2009-07-07 05:56:11 +04:00
void cfg80211_scan_done ( struct cfg80211_scan_request * request , bool aborted )
{
WARN_ON ( request ! = wiphy_to_dev ( request - > wiphy ) - > scan_req ) ;
request - > aborted = aborted ;
schedule_work ( & wiphy_to_dev ( request - > wiphy ) - > scan_done_wk ) ;
}
2009-02-10 23:25:55 +03:00
EXPORT_SYMBOL ( cfg80211_scan_done ) ;
static void bss_release ( struct kref * ref )
{
struct cfg80211_internal_bss * bss ;
bss = container_of ( ref , struct cfg80211_internal_bss , ref ) ;
2009-02-10 23:25:57 +03:00
if ( bss - > pub . free_priv )
bss - > pub . free_priv ( & bss - > pub ) ;
2009-04-16 17:00:58 +04:00
if ( bss - > ies_allocated )
kfree ( bss - > pub . information_elements ) ;
2009-07-02 19:20:43 +04:00
BUG_ON ( atomic_read ( & bss - > hold ) ) ;
2009-02-10 23:25:55 +03:00
kfree ( bss ) ;
}
2009-02-12 01:14:43 +03:00
/* must hold dev->bss_lock! */
void cfg80211_bss_age ( struct cfg80211_registered_device * dev ,
unsigned long age_secs )
{
struct cfg80211_internal_bss * bss ;
unsigned long age_jiffies = msecs_to_jiffies ( age_secs * MSEC_PER_SEC ) ;
list_for_each_entry ( bss , & dev - > bss_list , list ) {
bss - > ts - = age_jiffies ;
}
}
2009-02-10 23:25:55 +03:00
/* must hold dev->bss_lock! */
void cfg80211_bss_expire ( struct cfg80211_registered_device * dev )
{
struct cfg80211_internal_bss * bss , * tmp ;
bool expired = false ;
list_for_each_entry_safe ( bss , tmp , & dev - > bss_list , list ) {
2009-07-02 19:20:43 +04:00
if ( atomic_read ( & bss - > hold ) )
continue ;
if ( ! time_after ( jiffies , bss - > ts + IEEE80211_SCAN_RESULT_EXPIRE ) )
2009-02-10 23:25:55 +03:00
continue ;
list_del ( & bss - > list ) ;
rb_erase ( & bss - > rbn , & dev - > bss_tree ) ;
kref_put ( & bss - > ref , bss_release ) ;
expired = true ;
}
if ( expired )
dev - > bss_generation + + ;
}
2009-09-02 02:12:11 +04:00
static u8 * find_ie ( u8 num , u8 * ies , int len )
2009-02-10 23:25:55 +03:00
{
while ( len > 2 & & ies [ 0 ] ! = num ) {
len - = ies [ 1 ] + 2 ;
ies + = ies [ 1 ] + 2 ;
}
if ( len < 2 )
return NULL ;
if ( len < 2 + ies [ 1 ] )
return NULL ;
return ies ;
}
static int cmp_ies ( u8 num , u8 * ies1 , size_t len1 , u8 * ies2 , size_t len2 )
{
const u8 * ie1 = find_ie ( num , ies1 , len1 ) ;
const u8 * ie2 = find_ie ( num , ies2 , len2 ) ;
int r ;
if ( ! ie1 & & ! ie2 )
return 0 ;
2009-07-30 00:07:44 +04:00
if ( ! ie1 | | ! ie2 )
2009-02-10 23:25:55 +03:00
return - 1 ;
r = memcmp ( ie1 + 2 , ie2 + 2 , min ( ie1 [ 1 ] , ie2 [ 1 ] ) ) ;
if ( r = = 0 & & ie1 [ 1 ] ! = ie2 [ 1 ] )
return ie2 [ 1 ] - ie1 [ 1 ] ;
return r ;
}
static bool is_bss ( struct cfg80211_bss * a ,
const u8 * bssid ,
const u8 * ssid , size_t ssid_len )
{
const u8 * ssidie ;
2009-02-10 23:25:59 +03:00
if ( bssid & & compare_ether_addr ( a - > bssid , bssid ) )
2009-02-10 23:25:55 +03:00
return false ;
2009-02-10 23:25:59 +03:00
if ( ! ssid )
return true ;
2009-02-10 23:25:55 +03:00
ssidie = find_ie ( WLAN_EID_SSID ,
a - > information_elements ,
a - > len_information_elements ) ;
if ( ! ssidie )
return false ;
if ( ssidie [ 1 ] ! = ssid_len )
return false ;
return memcmp ( ssidie + 2 , ssid , ssid_len ) = = 0 ;
}
static bool is_mesh ( struct cfg80211_bss * a ,
const u8 * meshid , size_t meshidlen ,
const u8 * meshcfg )
{
const u8 * ie ;
if ( ! is_zero_ether_addr ( a - > bssid ) )
return false ;
ie = find_ie ( WLAN_EID_MESH_ID ,
a - > information_elements ,
a - > len_information_elements ) ;
if ( ! ie )
return false ;
if ( ie [ 1 ] ! = meshidlen )
return false ;
if ( memcmp ( ie + 2 , meshid , meshidlen ) )
return false ;
ie = find_ie ( WLAN_EID_MESH_CONFIG ,
a - > information_elements ,
a - > len_information_elements ) ;
2009-07-30 00:07:44 +04:00
if ( ! ie )
return false ;
2009-02-10 23:25:55 +03:00
if ( ie [ 1 ] ! = IEEE80211_MESH_CONFIG_LEN )
return false ;
/*
* Ignore mesh capability ( last two bytes of the IE ) when
* comparing since that may differ between stations taking
* part in the same mesh .
*/
return memcmp ( ie + 2 , meshcfg , IEEE80211_MESH_CONFIG_LEN - 2 ) = = 0 ;
}
static int cmp_bss ( struct cfg80211_bss * a ,
struct cfg80211_bss * b )
{
int r ;
if ( a - > channel ! = b - > channel )
return b - > channel - > center_freq - a - > channel - > center_freq ;
r = memcmp ( a - > bssid , b - > bssid , ETH_ALEN ) ;
if ( r )
return r ;
if ( is_zero_ether_addr ( a - > bssid ) ) {
r = cmp_ies ( WLAN_EID_MESH_ID ,
a - > information_elements ,
a - > len_information_elements ,
b - > information_elements ,
b - > len_information_elements ) ;
if ( r )
return r ;
return cmp_ies ( WLAN_EID_MESH_CONFIG ,
a - > information_elements ,
a - > len_information_elements ,
b - > information_elements ,
b - > len_information_elements ) ;
}
return cmp_ies ( WLAN_EID_SSID ,
a - > information_elements ,
a - > len_information_elements ,
b - > information_elements ,
b - > len_information_elements ) ;
}
struct cfg80211_bss * cfg80211_get_bss ( struct wiphy * wiphy ,
struct ieee80211_channel * channel ,
const u8 * bssid ,
2009-02-10 23:25:59 +03:00
const u8 * ssid , size_t ssid_len ,
u16 capa_mask , u16 capa_val )
2009-02-10 23:25:55 +03:00
{
struct cfg80211_registered_device * dev = wiphy_to_dev ( wiphy ) ;
struct cfg80211_internal_bss * bss , * res = NULL ;
spin_lock_bh ( & dev - > bss_lock ) ;
list_for_each_entry ( bss , & dev - > bss_list , list ) {
2009-02-10 23:25:59 +03:00
if ( ( bss - > pub . capability & capa_mask ) ! = capa_val )
continue ;
2009-02-10 23:25:55 +03:00
if ( channel & & bss - > pub . channel ! = channel )
continue ;
if ( is_bss ( & bss - > pub , bssid , ssid , ssid_len ) ) {
res = bss ;
kref_get ( & res - > ref ) ;
break ;
}
}
spin_unlock_bh ( & dev - > bss_lock ) ;
if ( ! res )
return NULL ;
return & res - > pub ;
}
EXPORT_SYMBOL ( cfg80211_get_bss ) ;
struct cfg80211_bss * cfg80211_get_mesh ( struct wiphy * wiphy ,
struct ieee80211_channel * channel ,
const u8 * meshid , size_t meshidlen ,
const u8 * meshcfg )
{
struct cfg80211_registered_device * dev = wiphy_to_dev ( wiphy ) ;
struct cfg80211_internal_bss * bss , * res = NULL ;
spin_lock_bh ( & dev - > bss_lock ) ;
list_for_each_entry ( bss , & dev - > bss_list , list ) {
if ( channel & & bss - > pub . channel ! = channel )
continue ;
if ( is_mesh ( & bss - > pub , meshid , meshidlen , meshcfg ) ) {
res = bss ;
kref_get ( & res - > ref ) ;
break ;
}
}
spin_unlock_bh ( & dev - > bss_lock ) ;
if ( ! res )
return NULL ;
return & res - > pub ;
}
EXPORT_SYMBOL ( cfg80211_get_mesh ) ;
static void rb_insert_bss ( struct cfg80211_registered_device * dev ,
struct cfg80211_internal_bss * bss )
{
struct rb_node * * p = & dev - > bss_tree . rb_node ;
struct rb_node * parent = NULL ;
struct cfg80211_internal_bss * tbss ;
int cmp ;
while ( * p ) {
parent = * p ;
tbss = rb_entry ( parent , struct cfg80211_internal_bss , rbn ) ;
cmp = cmp_bss ( & bss - > pub , & tbss - > pub ) ;
if ( WARN_ON ( ! cmp ) ) {
/* will sort of leak this BSS */
return ;
}
if ( cmp < 0 )
p = & ( * p ) - > rb_left ;
else
p = & ( * p ) - > rb_right ;
}
rb_link_node ( & bss - > rbn , parent , p ) ;
rb_insert_color ( & bss - > rbn , & dev - > bss_tree ) ;
}
static struct cfg80211_internal_bss *
rb_find_bss ( struct cfg80211_registered_device * dev ,
struct cfg80211_internal_bss * res )
{
struct rb_node * n = dev - > bss_tree . rb_node ;
struct cfg80211_internal_bss * bss ;
int r ;
while ( n ) {
bss = rb_entry ( n , struct cfg80211_internal_bss , rbn ) ;
r = cmp_bss ( & res - > pub , & bss - > pub ) ;
if ( r = = 0 )
return bss ;
else if ( r < 0 )
n = n - > rb_left ;
else
n = n - > rb_right ;
}
return NULL ;
}
static struct cfg80211_internal_bss *
cfg80211_bss_update ( struct cfg80211_registered_device * dev ,
struct cfg80211_internal_bss * res ,
bool overwrite )
{
struct cfg80211_internal_bss * found = NULL ;
const u8 * meshid , * meshcfg ;
/*
* The reference to " res " is donated to this function .
*/
if ( WARN_ON ( ! res - > pub . channel ) ) {
kref_put ( & res - > ref , bss_release ) ;
return NULL ;
}
res - > ts = jiffies ;
if ( is_zero_ether_addr ( res - > pub . bssid ) ) {
/* must be mesh, verify */
meshid = find_ie ( WLAN_EID_MESH_ID , res - > pub . information_elements ,
res - > pub . len_information_elements ) ;
meshcfg = find_ie ( WLAN_EID_MESH_CONFIG ,
res - > pub . information_elements ,
res - > pub . len_information_elements ) ;
if ( ! meshid | | ! meshcfg | |
meshcfg [ 1 ] ! = IEEE80211_MESH_CONFIG_LEN ) {
/* bogus mesh */
kref_put ( & res - > ref , bss_release ) ;
return NULL ;
}
}
spin_lock_bh ( & dev - > bss_lock ) ;
found = rb_find_bss ( dev , res ) ;
2009-04-16 17:00:58 +04:00
if ( found ) {
2009-02-10 23:25:55 +03:00
found - > pub . beacon_interval = res - > pub . beacon_interval ;
found - > pub . tsf = res - > pub . tsf ;
found - > pub . signal = res - > pub . signal ;
found - > pub . capability = res - > pub . capability ;
found - > ts = res - > ts ;
2009-04-16 17:00:58 +04:00
/* overwrite IEs */
if ( overwrite ) {
size_t used = dev - > wiphy . bss_priv_size + sizeof ( * res ) ;
size_t ielen = res - > pub . len_information_elements ;
2009-04-26 13:27:33 +04:00
if ( ! found - > ies_allocated & & ksize ( found ) > = used + ielen ) {
2009-04-16 17:00:58 +04:00
memcpy ( found - > pub . information_elements ,
res - > pub . information_elements , ielen ) ;
found - > pub . len_information_elements = ielen ;
} else {
u8 * ies = found - > pub . information_elements ;
2009-04-26 00:28:55 +04:00
if ( found - > ies_allocated )
ies = krealloc ( ies , ielen , GFP_ATOMIC ) ;
else
2009-04-16 17:00:58 +04:00
ies = kmalloc ( ielen , GFP_ATOMIC ) ;
if ( ies ) {
memcpy ( ies , res - > pub . information_elements , ielen ) ;
found - > ies_allocated = true ;
found - > pub . information_elements = ies ;
2009-04-30 22:09:56 +04:00
found - > pub . len_information_elements = ielen ;
2009-04-16 17:00:58 +04:00
}
}
}
2009-02-10 23:25:55 +03:00
kref_put ( & res - > ref , bss_release ) ;
} else {
/* this "consumes" the reference */
list_add_tail ( & res - > list , & dev - > bss_list ) ;
rb_insert_bss ( dev , res ) ;
found = res ;
}
dev - > bss_generation + + ;
spin_unlock_bh ( & dev - > bss_lock ) ;
kref_get ( & found - > ref ) ;
return found ;
}
2009-03-27 00:40:09 +03:00
struct cfg80211_bss *
cfg80211_inform_bss ( struct wiphy * wiphy ,
struct ieee80211_channel * channel ,
const u8 * bssid ,
u64 timestamp , u16 capability , u16 beacon_interval ,
const u8 * ie , size_t ielen ,
s32 signal , gfp_t gfp )
{
struct cfg80211_internal_bss * res ;
size_t privsz ;
if ( WARN_ON ( ! wiphy ) )
return NULL ;
privsz = wiphy - > bss_priv_size ;
if ( WARN_ON ( wiphy - > signal_type = = NL80211_BSS_SIGNAL_UNSPEC & &
( signal < 0 | | signal > 100 ) ) )
return NULL ;
res = kzalloc ( sizeof ( * res ) + privsz + ielen , gfp ) ;
if ( ! res )
return NULL ;
memcpy ( res - > pub . bssid , bssid , ETH_ALEN ) ;
res - > pub . channel = channel ;
res - > pub . signal = signal ;
res - > pub . tsf = timestamp ;
res - > pub . beacon_interval = beacon_interval ;
res - > pub . capability = capability ;
/* point to after the private area */
res - > pub . information_elements = ( u8 * ) res + sizeof ( * res ) + privsz ;
memcpy ( res - > pub . information_elements , ie , ielen ) ;
res - > pub . len_information_elements = ielen ;
kref_init ( & res - > ref ) ;
res = cfg80211_bss_update ( wiphy_to_dev ( wiphy ) , res , 0 ) ;
if ( ! res )
return NULL ;
if ( res - > pub . capability & WLAN_CAPABILITY_ESS )
regulatory_hint_found_beacon ( wiphy , channel , gfp ) ;
/* cfg80211_bss_update gives us a referenced result */
return & res - > pub ;
}
EXPORT_SYMBOL ( cfg80211_inform_bss ) ;
2009-02-10 23:25:55 +03:00
struct cfg80211_bss *
cfg80211_inform_bss_frame ( struct wiphy * wiphy ,
struct ieee80211_channel * channel ,
struct ieee80211_mgmt * mgmt , size_t len ,
2009-02-18 20:45:06 +03:00
s32 signal , gfp_t gfp )
2009-02-10 23:25:55 +03:00
{
struct cfg80211_internal_bss * res ;
size_t ielen = len - offsetof ( struct ieee80211_mgmt ,
u . probe_resp . variable ) ;
bool overwrite ;
size_t privsz = wiphy - > bss_priv_size ;
2009-02-18 20:45:06 +03:00
if ( WARN_ON ( wiphy - > signal_type = = NL80211_BSS_SIGNAL_UNSPEC & &
2009-02-10 23:25:55 +03:00
( signal < 0 | | signal > 100 ) ) )
return NULL ;
if ( WARN_ON ( ! mgmt | | ! wiphy | |
len < offsetof ( struct ieee80211_mgmt , u . probe_resp . variable ) ) )
return NULL ;
res = kzalloc ( sizeof ( * res ) + privsz + ielen , gfp ) ;
if ( ! res )
return NULL ;
memcpy ( res - > pub . bssid , mgmt - > bssid , ETH_ALEN ) ;
res - > pub . channel = channel ;
res - > pub . signal = signal ;
res - > pub . tsf = le64_to_cpu ( mgmt - > u . probe_resp . timestamp ) ;
res - > pub . beacon_interval = le16_to_cpu ( mgmt - > u . probe_resp . beacon_int ) ;
res - > pub . capability = le16_to_cpu ( mgmt - > u . probe_resp . capab_info ) ;
/* point to after the private area */
res - > pub . information_elements = ( u8 * ) res + sizeof ( * res ) + privsz ;
memcpy ( res - > pub . information_elements , mgmt - > u . probe_resp . variable , ielen ) ;
res - > pub . len_information_elements = ielen ;
kref_init ( & res - > ref ) ;
overwrite = ieee80211_is_probe_resp ( mgmt - > frame_control ) ;
res = cfg80211_bss_update ( wiphy_to_dev ( wiphy ) , res , overwrite ) ;
if ( ! res )
return NULL ;
2009-02-21 08:20:39 +03:00
if ( res - > pub . capability & WLAN_CAPABILITY_ESS )
regulatory_hint_found_beacon ( wiphy , channel , gfp ) ;
2009-02-10 23:25:55 +03:00
/* cfg80211_bss_update gives us a referenced result */
return & res - > pub ;
}
EXPORT_SYMBOL ( cfg80211_inform_bss_frame ) ;
void cfg80211_put_bss ( struct cfg80211_bss * pub )
{
struct cfg80211_internal_bss * bss ;
if ( ! pub )
return ;
bss = container_of ( pub , struct cfg80211_internal_bss , pub ) ;
kref_put ( & bss - > ref , bss_release ) ;
}
EXPORT_SYMBOL ( cfg80211_put_bss ) ;
2009-02-10 23:25:58 +03:00
void cfg80211_unlink_bss ( struct wiphy * wiphy , struct cfg80211_bss * pub )
{
struct cfg80211_registered_device * dev = wiphy_to_dev ( wiphy ) ;
struct cfg80211_internal_bss * bss ;
if ( WARN_ON ( ! pub ) )
return ;
bss = container_of ( pub , struct cfg80211_internal_bss , pub ) ;
spin_lock_bh ( & dev - > bss_lock ) ;
list_del ( & bss - > list ) ;
2009-08-07 18:17:38 +04:00
dev - > bss_generation + + ;
2009-02-10 23:25:58 +03:00
rb_erase ( & bss - > rbn , & dev - > bss_tree ) ;
spin_unlock_bh ( & dev - > bss_lock ) ;
kref_put ( & bss - > ref , bss_release ) ;
}
EXPORT_SYMBOL ( cfg80211_unlink_bss ) ;
2009-02-10 23:25:55 +03:00
# ifdef CONFIG_WIRELESS_EXT
int cfg80211_wext_siwscan ( struct net_device * dev ,
struct iw_request_info * info ,
union iwreq_data * wrqu , char * extra )
{
struct cfg80211_registered_device * rdev ;
struct wiphy * wiphy ;
struct iw_scan_req * wreq = NULL ;
struct cfg80211_scan_request * creq ;
int i , err , n_channels = 0 ;
enum ieee80211_band band ;
if ( ! netif_running ( dev ) )
return - ENETDOWN ;
2009-09-09 15:09:54 +04:00
if ( wrqu - > data . length = = sizeof ( struct iw_scan_req ) )
wreq = ( struct iw_scan_req * ) extra ;
2009-07-14 02:33:35 +04:00
rdev = cfg80211_get_dev_from_ifindex ( dev_net ( dev ) , dev - > ifindex ) ;
2009-02-10 23:25:55 +03:00
if ( IS_ERR ( rdev ) )
return PTR_ERR ( rdev ) ;
if ( rdev - > scan_req ) {
err = - EBUSY ;
goto out ;
}
wiphy = & rdev - > wiphy ;
2009-09-09 15:09:54 +04:00
/* Determine number of channels, needed to allocate creq */
if ( wreq & & wreq - > num_channels )
n_channels = wreq - > num_channels ;
else {
for ( band = 0 ; band < IEEE80211_NUM_BANDS ; band + + )
if ( wiphy - > bands [ band ] )
n_channels + = wiphy - > bands [ band ] - > n_channels ;
}
2009-02-10 23:25:55 +03:00
creq = kzalloc ( sizeof ( * creq ) + sizeof ( struct cfg80211_ssid ) +
n_channels * sizeof ( void * ) ,
GFP_ATOMIC ) ;
if ( ! creq ) {
err = - ENOMEM ;
goto out ;
}
creq - > wiphy = wiphy ;
2009-07-14 02:33:35 +04:00
creq - > dev = dev ;
2009-08-07 19:54:07 +04:00
/* SSIDs come after channels */
creq - > ssids = ( void * ) & creq - > channels [ n_channels ] ;
2009-02-10 23:25:55 +03:00
creq - > n_channels = n_channels ;
creq - > n_ssids = 1 ;
2009-09-09 15:09:54 +04:00
/* translate "Scan on frequencies" request */
2009-02-10 23:25:55 +03:00
i = 0 ;
for ( band = 0 ; band < IEEE80211_NUM_BANDS ; band + + ) {
int j ;
if ( ! wiphy - > bands [ band ] )
continue ;
for ( j = 0 ; j < wiphy - > bands [ band ] - > n_channels ; j + + ) {
2009-09-09 15:09:54 +04:00
/* If we have a wireless request structure and the
* wireless request specifies frequencies , then search
* for the matching hardware channel .
*/
if ( wreq & & wreq - > num_channels ) {
int k ;
int wiphy_freq = wiphy - > bands [ band ] - > channels [ j ] . center_freq ;
for ( k = 0 ; k < wreq - > num_channels ; k + + ) {
2009-09-11 12:13:53 +04:00
int wext_freq = cfg80211_wext_freq ( wiphy , & wreq - > channel_list [ k ] ) ;
2009-09-09 15:09:54 +04:00
if ( wext_freq = = wiphy_freq )
goto wext_freq_found ;
}
goto wext_freq_not_found ;
}
wext_freq_found :
2009-02-10 23:25:55 +03:00
creq - > channels [ i ] = & wiphy - > bands [ band ] - > channels [ j ] ;
i + + ;
2009-09-09 15:09:54 +04:00
wext_freq_not_found : ;
2009-02-10 23:25:55 +03:00
}
}
2009-09-09 15:09:54 +04:00
/* Set real number of channels specified in creq->channels[] */
creq - > n_channels = i ;
2009-02-10 23:25:55 +03:00
2009-09-09 15:09:54 +04:00
/* translate "Scan for SSID" request */
if ( wreq ) {
2009-02-10 23:25:55 +03:00
if ( wrqu - > data . flags & IW_SCAN_THIS_ESSID ) {
if ( wreq - > essid_len > IEEE80211_MAX_SSID_LEN )
return - EINVAL ;
memcpy ( creq - > ssids [ 0 ] . ssid , wreq - > essid , wreq - > essid_len ) ;
creq - > ssids [ 0 ] . ssid_len = wreq - > essid_len ;
}
if ( wreq - > scan_type = = IW_SCAN_TYPE_PASSIVE )
creq - > n_ssids = 0 ;
}
rdev - > scan_req = creq ;
err = rdev - > ops - > scan ( wiphy , dev , creq ) ;
if ( err ) {
rdev - > scan_req = NULL ;
kfree ( creq ) ;
2009-07-14 02:33:35 +04:00
} else {
2009-06-16 21:56:42 +04:00
nl80211_send_scan_start ( rdev , dev ) ;
2009-07-14 02:33:35 +04:00
dev_hold ( dev ) ;
}
2009-02-10 23:25:55 +03:00
out :
2009-07-07 05:56:09 +04:00
cfg80211_unlock_rdev ( rdev ) ;
2009-02-10 23:25:55 +03:00
return err ;
}
2009-04-20 20:49:39 +04:00
EXPORT_SYMBOL_GPL ( cfg80211_wext_siwscan ) ;
2009-02-10 23:25:55 +03:00
static void ieee80211_scan_add_ies ( struct iw_request_info * info ,
struct cfg80211_bss * bss ,
char * * current_ev , char * end_buf )
{
u8 * pos , * end , * next ;
struct iw_event iwe ;
if ( ! bss - > information_elements | |
! bss - > len_information_elements )
return ;
/*
* If needed , fragment the IEs buffer ( at IE boundaries ) into short
* enough fragments to fit into IW_GENERIC_IE_MAX octet messages .
*/
pos = bss - > information_elements ;
end = pos + bss - > len_information_elements ;
while ( end - pos > IW_GENERIC_IE_MAX ) {
next = pos + 2 + pos [ 1 ] ;
while ( next + 2 + next [ 1 ] - pos < IW_GENERIC_IE_MAX )
next = next + 2 + next [ 1 ] ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = IWEVGENIE ;
iwe . u . data . length = next - pos ;
* current_ev = iwe_stream_add_point ( info , * current_ev ,
end_buf , & iwe , pos ) ;
pos = next ;
}
if ( end > pos ) {
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = IWEVGENIE ;
iwe . u . data . length = end - pos ;
* current_ev = iwe_stream_add_point ( info , * current_ev ,
end_buf , & iwe , pos ) ;
}
}
2009-02-12 01:14:43 +03:00
static inline unsigned int elapsed_jiffies_msecs ( unsigned long start )
{
unsigned long end = jiffies ;
if ( end > = start )
return jiffies_to_msecs ( end - start ) ;
return jiffies_to_msecs ( end + ( MAX_JIFFY_OFFSET - start ) + 1 ) ;
}
2009-02-10 23:25:55 +03:00
static char *
2009-02-18 20:45:06 +03:00
ieee80211_bss ( struct wiphy * wiphy , struct iw_request_info * info ,
struct cfg80211_internal_bss * bss , char * current_ev ,
char * end_buf )
2009-02-10 23:25:55 +03:00
{
struct iw_event iwe ;
u8 * buf , * cfg , * p ;
u8 * ie = bss - > pub . information_elements ;
2009-02-18 20:27:22 +03:00
int rem = bss - > pub . len_information_elements , i , sig ;
2009-02-10 23:25:55 +03:00
bool ismesh = false ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWAP ;
iwe . u . ap_addr . sa_family = ARPHRD_ETHER ;
memcpy ( iwe . u . ap_addr . sa_data , bss - > pub . bssid , ETH_ALEN ) ;
current_ev = iwe_stream_add_event ( info , current_ev , end_buf , & iwe ,
IW_EV_ADDR_LEN ) ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWFREQ ;
iwe . u . freq . m = ieee80211_frequency_to_channel ( bss - > pub . channel - > center_freq ) ;
iwe . u . freq . e = 0 ;
current_ev = iwe_stream_add_event ( info , current_ev , end_buf , & iwe ,
IW_EV_FREQ_LEN ) ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWFREQ ;
iwe . u . freq . m = bss - > pub . channel - > center_freq ;
iwe . u . freq . e = 6 ;
current_ev = iwe_stream_add_event ( info , current_ev , end_buf , & iwe ,
IW_EV_FREQ_LEN ) ;
2009-02-18 20:45:06 +03:00
if ( wiphy - > signal_type ! = CFG80211_SIGNAL_TYPE_NONE ) {
2009-02-10 23:25:55 +03:00
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = IWEVQUAL ;
iwe . u . qual . updated = IW_QUAL_LEVEL_UPDATED |
IW_QUAL_NOISE_INVALID |
2009-02-18 20:27:22 +03:00
IW_QUAL_QUAL_UPDATED ;
2009-02-18 20:45:06 +03:00
switch ( wiphy - > signal_type ) {
2009-02-10 23:25:55 +03:00
case CFG80211_SIGNAL_TYPE_MBM :
2009-02-18 20:27:22 +03:00
sig = bss - > pub . signal / 100 ;
iwe . u . qual . level = sig ;
2009-02-10 23:25:55 +03:00
iwe . u . qual . updated | = IW_QUAL_DBM ;
2009-02-18 20:27:22 +03:00
if ( sig < - 110 ) /* rather bad */
sig = - 110 ;
else if ( sig > - 40 ) /* perfect */
sig = - 40 ;
/* will give a range of 0 .. 70 */
iwe . u . qual . qual = sig + 110 ;
2009-02-10 23:25:55 +03:00
break ;
case CFG80211_SIGNAL_TYPE_UNSPEC :
iwe . u . qual . level = bss - > pub . signal ;
2009-02-18 20:27:22 +03:00
/* will give range 0 .. 100 */
iwe . u . qual . qual = bss - > pub . signal ;
2009-02-10 23:25:55 +03:00
break ;
default :
/* not reached */
break ;
}
current_ev = iwe_stream_add_event ( info , current_ev , end_buf ,
& iwe , IW_EV_QUAL_LEN ) ;
}
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWENCODE ;
if ( bss - > pub . capability & WLAN_CAPABILITY_PRIVACY )
iwe . u . data . flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY ;
else
iwe . u . data . flags = IW_ENCODE_DISABLED ;
iwe . u . data . length = 0 ;
current_ev = iwe_stream_add_point ( info , current_ev , end_buf ,
& iwe , " " ) ;
while ( rem > = 2 ) {
/* invalid data */
if ( ie [ 1 ] > rem - 2 )
break ;
switch ( ie [ 0 ] ) {
case WLAN_EID_SSID :
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWESSID ;
iwe . u . data . length = ie [ 1 ] ;
iwe . u . data . flags = 1 ;
current_ev = iwe_stream_add_point ( info , current_ev , end_buf ,
& iwe , ie + 2 ) ;
break ;
case WLAN_EID_MESH_ID :
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWESSID ;
iwe . u . data . length = ie [ 1 ] ;
iwe . u . data . flags = 1 ;
current_ev = iwe_stream_add_point ( info , current_ev , end_buf ,
& iwe , ie + 2 ) ;
break ;
case WLAN_EID_MESH_CONFIG :
ismesh = true ;
if ( ie [ 1 ] ! = IEEE80211_MESH_CONFIG_LEN )
break ;
buf = kmalloc ( 50 , GFP_ATOMIC ) ;
if ( ! buf )
break ;
cfg = ie + 2 ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = IWEVCUSTOM ;
sprintf ( buf , " Mesh network (version %d) " , cfg [ 0 ] ) ;
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev ,
end_buf ,
& iwe , buf ) ;
sprintf ( buf , " Path Selection Protocol ID: "
" 0x%02X%02X%02X%02X " , cfg [ 1 ] , cfg [ 2 ] , cfg [ 3 ] ,
cfg [ 4 ] ) ;
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev ,
end_buf ,
& iwe , buf ) ;
sprintf ( buf , " Path Selection Metric ID: "
" 0x%02X%02X%02X%02X " , cfg [ 5 ] , cfg [ 6 ] , cfg [ 7 ] ,
cfg [ 8 ] ) ;
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev ,
end_buf ,
& iwe , buf ) ;
sprintf ( buf , " Congestion Control Mode ID: "
" 0x%02X%02X%02X%02X " , cfg [ 9 ] , cfg [ 10 ] ,
cfg [ 11 ] , cfg [ 12 ] ) ;
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev ,
end_buf ,
& iwe , buf ) ;
sprintf ( buf , " Channel Precedence: "
" 0x%02X%02X%02X%02X " , cfg [ 13 ] , cfg [ 14 ] ,
cfg [ 15 ] , cfg [ 16 ] ) ;
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev ,
end_buf ,
& iwe , buf ) ;
kfree ( buf ) ;
break ;
case WLAN_EID_SUPP_RATES :
case WLAN_EID_EXT_SUPP_RATES :
/* display all supported rates in readable format */
p = current_ev + iwe_stream_lcp_len ( info ) ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWRATE ;
/* Those two flags are ignored... */
iwe . u . bitrate . fixed = iwe . u . bitrate . disabled = 0 ;
for ( i = 0 ; i < ie [ 1 ] ; i + + ) {
iwe . u . bitrate . value =
( ( ie [ i + 2 ] & 0x7f ) * 500000 ) ;
p = iwe_stream_add_value ( info , current_ev , p ,
end_buf , & iwe , IW_EV_PARAM_LEN ) ;
}
current_ev = p ;
break ;
}
rem - = ie [ 1 ] + 2 ;
ie + = ie [ 1 ] + 2 ;
}
if ( bss - > pub . capability & ( WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS )
| | ismesh ) {
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = SIOCGIWMODE ;
if ( ismesh )
iwe . u . mode = IW_MODE_MESH ;
else if ( bss - > pub . capability & WLAN_CAPABILITY_ESS )
iwe . u . mode = IW_MODE_MASTER ;
else
iwe . u . mode = IW_MODE_ADHOC ;
current_ev = iwe_stream_add_event ( info , current_ev , end_buf ,
& iwe , IW_EV_UINT_LEN ) ;
}
buf = kmalloc ( 30 , GFP_ATOMIC ) ;
if ( buf ) {
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = IWEVCUSTOM ;
sprintf ( buf , " tsf=%016llx " , ( unsigned long long ) ( bss - > pub . tsf ) ) ;
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev , end_buf ,
& iwe , buf ) ;
memset ( & iwe , 0 , sizeof ( iwe ) ) ;
iwe . cmd = IWEVCUSTOM ;
2009-02-12 01:14:43 +03:00
sprintf ( buf , " Last beacon: %ums ago " ,
elapsed_jiffies_msecs ( bss - > ts ) ) ;
2009-02-10 23:25:55 +03:00
iwe . u . data . length = strlen ( buf ) ;
current_ev = iwe_stream_add_point ( info , current_ev ,
end_buf , & iwe , buf ) ;
kfree ( buf ) ;
}
ieee80211_scan_add_ies ( info , & bss - > pub , & current_ev , end_buf ) ;
return current_ev ;
}
static int ieee80211_scan_results ( struct cfg80211_registered_device * dev ,
struct iw_request_info * info ,
char * buf , size_t len )
{
char * current_ev = buf ;
char * end_buf = buf + len ;
struct cfg80211_internal_bss * bss ;
spin_lock_bh ( & dev - > bss_lock ) ;
cfg80211_bss_expire ( dev ) ;
list_for_each_entry ( bss , & dev - > bss_list , list ) {
if ( buf + len - current_ev < = IW_EV_ADDR_LEN ) {
spin_unlock_bh ( & dev - > bss_lock ) ;
return - E2BIG ;
}
2009-02-18 20:45:06 +03:00
current_ev = ieee80211_bss ( & dev - > wiphy , info , bss ,
current_ev , end_buf ) ;
2009-02-10 23:25:55 +03:00
}
spin_unlock_bh ( & dev - > bss_lock ) ;
return current_ev - buf ;
}
int cfg80211_wext_giwscan ( struct net_device * dev ,
struct iw_request_info * info ,
struct iw_point * data , char * extra )
{
struct cfg80211_registered_device * rdev ;
int res ;
if ( ! netif_running ( dev ) )
return - ENETDOWN ;
2009-07-14 02:33:35 +04:00
rdev = cfg80211_get_dev_from_ifindex ( dev_net ( dev ) , dev - > ifindex ) ;
2009-02-10 23:25:55 +03:00
if ( IS_ERR ( rdev ) )
return PTR_ERR ( rdev ) ;
if ( rdev - > scan_req ) {
res = - EAGAIN ;
goto out ;
}
res = ieee80211_scan_results ( rdev , info , extra , data - > length ) ;
data - > length = 0 ;
if ( res > = 0 ) {
data - > length = res ;
res = 0 ;
}
out :
2009-07-07 05:56:09 +04:00
cfg80211_unlock_rdev ( rdev ) ;
2009-02-10 23:25:55 +03:00
return res ;
}
2009-04-20 20:49:39 +04:00
EXPORT_SYMBOL_GPL ( cfg80211_wext_giwscan ) ;
2009-02-10 23:25:55 +03:00
# endif