2007-05-05 22:45:53 +04:00
/*
* Copyright 2002 - 2005 , Instant802 Networks , Inc .
* Copyright 2006 - 2007 Jiri Benc < jbenc @ suse . cz >
*
* 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 .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/netdevice.h>
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/skbuff.h>
# include <linux/if_arp.h>
2007-12-17 17:07:43 +03:00
# include <linux/timer.h>
2007-05-05 22:45:53 +04:00
# include <net/mac80211.h>
# include "ieee80211_i.h"
# include "ieee80211_rate.h"
# include "sta_info.h"
2007-05-05 22:46:38 +04:00
# include "debugfs_sta.h"
2007-05-05 22:45:53 +04:00
/* Caller must hold local->sta_lock */
static void sta_info_hash_add ( struct ieee80211_local * local ,
struct sta_info * sta )
{
sta - > hnext = local - > sta_hash [ STA_HASH ( sta - > addr ) ] ;
local - > sta_hash [ STA_HASH ( sta - > addr ) ] = sta ;
}
/* Caller must hold local->sta_lock */
2007-07-27 17:43:23 +04:00
static int sta_info_hash_del ( struct ieee80211_local * local ,
struct sta_info * sta )
2007-05-05 22:45:53 +04:00
{
struct sta_info * s ;
s = local - > sta_hash [ STA_HASH ( sta - > addr ) ] ;
if ( ! s )
2007-07-27 17:43:23 +04:00
return - ENOENT ;
if ( s = = sta ) {
2007-05-05 22:45:53 +04:00
local - > sta_hash [ STA_HASH ( sta - > addr ) ] = s - > hnext ;
2007-07-27 17:43:23 +04:00
return 0 ;
2007-05-05 22:45:53 +04:00
}
2007-07-27 17:43:23 +04:00
while ( s - > hnext & & s - > hnext ! = sta )
2007-05-05 22:45:53 +04:00
s = s - > hnext ;
2007-07-27 17:43:23 +04:00
if ( s - > hnext ) {
s - > hnext = sta - > hnext ;
return 0 ;
}
2007-05-05 22:45:53 +04:00
2007-07-27 17:43:23 +04:00
return - ENOENT ;
2007-05-05 22:45:53 +04:00
}
struct sta_info * sta_info_get ( struct ieee80211_local * local , u8 * addr )
{
struct sta_info * sta ;
2007-07-27 17:43:23 +04:00
read_lock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
sta = local - > sta_hash [ STA_HASH ( addr ) ] ;
while ( sta ) {
if ( memcmp ( sta - > addr , addr , ETH_ALEN ) = = 0 ) {
__sta_info_get ( sta ) ;
break ;
}
sta = sta - > hnext ;
}
2007-07-27 17:43:23 +04:00
read_unlock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
return sta ;
}
EXPORT_SYMBOL ( sta_info_get ) ;
int sta_info_min_txrate_get ( struct ieee80211_local * local )
{
struct sta_info * sta ;
struct ieee80211_hw_mode * mode ;
int min_txrate = 9999999 ;
int i ;
2007-07-27 17:43:23 +04:00
read_lock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
mode = local - > oper_hw_mode ;
for ( i = 0 ; i < STA_HASH_SIZE ; i + + ) {
sta = local - > sta_hash [ i ] ;
while ( sta ) {
if ( sta - > txrate < min_txrate )
min_txrate = sta - > txrate ;
sta = sta - > hnext ;
}
}
2007-07-27 17:43:23 +04:00
read_unlock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
if ( min_txrate = = 9999999 )
min_txrate = 0 ;
return mode - > rates [ min_txrate ] . rate ;
}
static void sta_info_release ( struct kref * kref )
{
struct sta_info * sta = container_of ( kref , struct sta_info , kref ) ;
struct ieee80211_local * local = sta - > local ;
struct sk_buff * skb ;
/* free sta structure; it has already been removed from
* hash table etc . external structures . Make sure that all
* buffered frames are release ( one might have been added
* after sta_info_free ( ) was called ) . */
while ( ( skb = skb_dequeue ( & sta - > ps_tx_buf ) ) ! = NULL ) {
local - > total_ps_buffered - - ;
dev_kfree_skb_any ( skb ) ;
}
while ( ( skb = skb_dequeue ( & sta - > tx_filtered ) ) ! = NULL ) {
dev_kfree_skb_any ( skb ) ;
}
rate_control_free_sta ( sta - > rate_ctrl , sta - > rate_ctrl_priv ) ;
rate_control_put ( sta - > rate_ctrl ) ;
kfree ( sta ) ;
}
void sta_info_put ( struct sta_info * sta )
{
kref_put ( & sta - > kref , sta_info_release ) ;
}
EXPORT_SYMBOL ( sta_info_put ) ;
struct sta_info * sta_info_add ( struct ieee80211_local * local ,
struct net_device * dev , u8 * addr , gfp_t gfp )
{
struct sta_info * sta ;
2007-10-04 04:59:30 +04:00
DECLARE_MAC_BUF ( mac ) ;
2007-05-05 22:45:53 +04:00
sta = kzalloc ( sizeof ( * sta ) , gfp ) ;
if ( ! sta )
return NULL ;
kref_init ( & sta - > kref ) ;
sta - > rate_ctrl = rate_control_get ( local - > rate_ctrl ) ;
sta - > rate_ctrl_priv = rate_control_alloc_sta ( sta - > rate_ctrl , gfp ) ;
if ( ! sta - > rate_ctrl_priv ) {
rate_control_put ( sta - > rate_ctrl ) ;
kfree ( sta ) ;
return NULL ;
}
memcpy ( sta - > addr , addr , ETH_ALEN ) ;
sta - > local = local ;
sta - > dev = dev ;
skb_queue_head_init ( & sta - > ps_tx_buf ) ;
skb_queue_head_init ( & sta - > tx_filtered ) ;
__sta_info_get ( sta ) ; /* sta used by caller, decremented by
* sta_info_put ( ) */
2007-07-27 17:43:23 +04:00
write_lock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
list_add ( & sta - > list , & local - > sta_list ) ;
local - > num_sta + + ;
sta_info_hash_add ( local , sta ) ;
2007-09-30 15:52:37 +04:00
if ( local - > ops - > sta_notify )
local - > ops - > sta_notify ( local_to_hw ( local ) , dev - > ifindex ,
STA_NOTIFY_ADD , addr ) ;
2007-07-27 17:43:23 +04:00
write_unlock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
# ifdef CONFIG_MAC80211_VERBOSE_DEBUG
2007-10-04 04:59:30 +04:00
printk ( KERN_DEBUG " %s: Added STA %s \n " ,
2007-09-19 01:29:20 +04:00
wiphy_name ( local - > hw . wiphy ) , print_mac ( mac , addr ) ) ;
2007-05-05 22:45:53 +04:00
# endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
2007-05-05 22:46:38 +04:00
# ifdef CONFIG_MAC80211_DEBUGFS
2007-07-27 17:43:23 +04:00
/* debugfs entry adding might sleep, so schedule process
* context task for adding entry for STAs that do not yet
* have one . */
queue_work ( local - > hw . workqueue , & local - > sta_debugfs_add ) ;
2007-05-05 22:46:38 +04:00
# endif
2007-05-05 22:45:53 +04:00
return sta ;
}
2007-07-27 17:43:23 +04:00
/* Caller must hold local->sta_lock */
void sta_info_remove ( struct sta_info * sta )
2007-05-05 22:45:53 +04:00
{
struct ieee80211_local * local = sta - > local ;
struct ieee80211_sub_if_data * sdata ;
2007-07-27 17:43:23 +04:00
/* don't do anything if we've been removed already */
if ( sta_info_hash_del ( local , sta ) )
return ;
2007-05-05 22:45:53 +04:00
list_del ( & sta - > list ) ;
sdata = IEEE80211_DEV_TO_SUB_IF ( sta - > dev ) ;
if ( sta - > flags & WLAN_STA_PS ) {
sta - > flags & = ~ WLAN_STA_PS ;
if ( sdata - > bss )
atomic_dec ( & sdata - > bss - > num_sta_ps ) ;
}
local - > num_sta - - ;
sta_info_remove_aid_ptr ( sta ) ;
2007-07-27 17:43:23 +04:00
2007-05-05 22:45:53 +04:00
}
2007-07-27 17:43:23 +04:00
void sta_info_free ( struct sta_info * sta )
2007-05-05 22:45:53 +04:00
{
struct sk_buff * skb ;
struct ieee80211_local * local = sta - > local ;
2007-10-04 04:59:30 +04:00
DECLARE_MAC_BUF ( mac ) ;
2007-05-05 22:45:53 +04:00
2007-07-27 17:43:23 +04:00
might_sleep ( ) ;
write_lock_bh ( & local - > sta_lock ) ;
sta_info_remove ( sta ) ;
write_unlock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
while ( ( skb = skb_dequeue ( & sta - > ps_tx_buf ) ) ! = NULL ) {
local - > total_ps_buffered - - ;
2007-07-27 17:43:23 +04:00
dev_kfree_skb ( skb ) ;
2007-05-05 22:45:53 +04:00
}
while ( ( skb = skb_dequeue ( & sta - > tx_filtered ) ) ! = NULL ) {
2007-07-27 17:43:23 +04:00
dev_kfree_skb ( skb ) ;
2007-05-05 22:45:53 +04:00
}
2007-07-27 17:43:23 +04:00
# ifdef CONFIG_MAC80211_VERBOSE_DEBUG
2007-10-04 04:59:30 +04:00
printk ( KERN_DEBUG " %s: Removed STA %s \n " ,
2007-09-19 01:29:20 +04:00
wiphy_name ( local - > hw . wiphy ) , print_mac ( mac , sta - > addr ) ) ;
2007-07-27 17:43:23 +04:00
# endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
2007-08-29 01:01:55 +04:00
ieee80211_key_free ( sta - > key ) ;
sta - > key = NULL ;
2007-07-27 17:43:23 +04:00
2007-09-30 15:52:37 +04:00
if ( local - > ops - > sta_notify )
local - > ops - > sta_notify ( local_to_hw ( local ) , sta - > dev - > ifindex ,
STA_NOTIFY_REMOVE , sta - > addr ) ;
2007-07-27 17:43:23 +04:00
rate_control_remove_sta_debugfs ( sta ) ;
ieee80211_sta_debugfs_remove ( sta ) ;
sta_info_put ( sta ) ;
2007-05-05 22:45:53 +04:00
}
static inline int sta_info_buffer_expired ( struct ieee80211_local * local ,
struct sta_info * sta ,
struct sk_buff * skb )
{
struct ieee80211_tx_packet_data * pkt_data ;
int timeout ;
if ( ! skb )
return 0 ;
pkt_data = ( struct ieee80211_tx_packet_data * ) skb - > cb ;
/* Timeout: (2 * listen_interval * beacon_int * 1024 / 1000000) sec */
timeout = ( sta - > listen_interval * local - > hw . conf . beacon_int * 32 /
15625 ) * HZ ;
if ( timeout < STA_TX_BUFFER_EXPIRE )
timeout = STA_TX_BUFFER_EXPIRE ;
return time_after ( jiffies , pkt_data - > jiffies + timeout ) ;
}
static void sta_info_cleanup_expire_buffered ( struct ieee80211_local * local ,
struct sta_info * sta )
{
unsigned long flags ;
struct sk_buff * skb ;
2007-10-04 04:59:30 +04:00
DECLARE_MAC_BUF ( mac ) ;
2007-05-05 22:45:53 +04:00
if ( skb_queue_empty ( & sta - > ps_tx_buf ) )
return ;
for ( ; ; ) {
spin_lock_irqsave ( & sta - > ps_tx_buf . lock , flags ) ;
skb = skb_peek ( & sta - > ps_tx_buf ) ;
if ( sta_info_buffer_expired ( local , sta , skb ) ) {
skb = __skb_dequeue ( & sta - > ps_tx_buf ) ;
if ( skb_queue_empty ( & sta - > ps_tx_buf ) )
sta - > flags & = ~ WLAN_STA_TIM ;
} else
skb = NULL ;
spin_unlock_irqrestore ( & sta - > ps_tx_buf . lock , flags ) ;
if ( skb ) {
local - > total_ps_buffered - - ;
printk ( KERN_DEBUG " Buffered frame expired (STA "
2007-10-04 04:59:30 +04:00
" %s) \n " , print_mac ( mac , sta - > addr ) ) ;
2007-05-05 22:45:53 +04:00
dev_kfree_skb ( skb ) ;
} else
break ;
}
}
static void sta_info_cleanup ( unsigned long data )
{
struct ieee80211_local * local = ( struct ieee80211_local * ) data ;
struct sta_info * sta ;
2007-07-27 17:43:23 +04:00
read_lock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
list_for_each_entry ( sta , & local - > sta_list , list ) {
__sta_info_get ( sta ) ;
sta_info_cleanup_expire_buffered ( local , sta ) ;
sta_info_put ( sta ) ;
}
2007-07-27 17:43:23 +04:00
read_unlock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
2007-12-17 17:07:43 +03:00
local - > sta_cleanup . expires =
round_jiffies ( jiffies + STA_INFO_CLEANUP_INTERVAL ) ;
2007-05-05 22:45:53 +04:00
add_timer ( & local - > sta_cleanup ) ;
}
2007-05-05 22:46:38 +04:00
# ifdef CONFIG_MAC80211_DEBUGFS
static void sta_info_debugfs_add_task ( struct work_struct * work )
{
struct ieee80211_local * local =
container_of ( work , struct ieee80211_local , sta_debugfs_add ) ;
struct sta_info * sta , * tmp ;
while ( 1 ) {
sta = NULL ;
2007-07-27 17:43:23 +04:00
read_lock_bh ( & local - > sta_lock ) ;
2007-05-05 22:46:38 +04:00
list_for_each_entry ( tmp , & local - > sta_list , list ) {
2007-07-27 17:43:23 +04:00
if ( ! tmp - > debugfs . dir ) {
2007-05-05 22:46:38 +04:00
sta = tmp ;
__sta_info_get ( sta ) ;
break ;
}
}
2007-07-27 17:43:23 +04:00
read_unlock_bh ( & local - > sta_lock ) ;
2007-05-05 22:46:38 +04:00
if ( ! sta )
break ;
ieee80211_sta_debugfs_add ( sta ) ;
rate_control_add_sta_debugfs ( sta ) ;
sta_info_put ( sta ) ;
}
}
# endif
2007-05-05 22:45:53 +04:00
void sta_info_init ( struct ieee80211_local * local )
{
2007-07-27 17:43:23 +04:00
rwlock_init ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
INIT_LIST_HEAD ( & local - > sta_list ) ;
2008-01-24 08:20:07 +03:00
setup_timer ( & local - > sta_cleanup , sta_info_cleanup ,
( unsigned long ) local ) ;
2007-12-17 17:07:43 +03:00
local - > sta_cleanup . expires =
round_jiffies ( jiffies + STA_INFO_CLEANUP_INTERVAL ) ;
2007-05-05 22:46:38 +04:00
# ifdef CONFIG_MAC80211_DEBUGFS
INIT_WORK ( & local - > sta_debugfs_add , sta_info_debugfs_add_task ) ;
# endif
2007-05-05 22:45:53 +04:00
}
int sta_info_start ( struct ieee80211_local * local )
{
add_timer ( & local - > sta_cleanup ) ;
return 0 ;
}
void sta_info_stop ( struct ieee80211_local * local )
{
del_timer ( & local - > sta_cleanup ) ;
2007-07-27 17:43:23 +04:00
sta_info_flush ( local , NULL ) ;
2007-05-05 22:45:53 +04:00
}
void sta_info_remove_aid_ptr ( struct sta_info * sta )
{
struct ieee80211_sub_if_data * sdata ;
if ( sta - > aid < = 0 )
return ;
sdata = IEEE80211_DEV_TO_SUB_IF ( sta - > dev ) ;
if ( sdata - > local - > ops - > set_tim )
sdata - > local - > ops - > set_tim ( local_to_hw ( sdata - > local ) ,
sta - > aid , 0 ) ;
if ( sdata - > bss )
__bss_tim_clear ( sdata - > bss , sta - > aid ) ;
}
/**
* sta_info_flush - flush matching STA entries from the STA table
* @ local : local interface data
* @ dev : matching rule for the net device ( sta - > dev ) or % NULL to match all STAs
*/
void sta_info_flush ( struct ieee80211_local * local , struct net_device * dev )
{
struct sta_info * sta , * tmp ;
2007-07-27 17:43:23 +04:00
LIST_HEAD ( tmp_list ) ;
2007-05-05 22:45:53 +04:00
2007-07-27 17:43:23 +04:00
write_lock_bh ( & local - > sta_lock ) ;
2007-05-05 22:45:53 +04:00
list_for_each_entry_safe ( sta , tmp , & local - > sta_list , list )
2007-07-27 17:43:23 +04:00
if ( ! dev | | dev = = sta - > dev ) {
__sta_info_get ( sta ) ;
sta_info_remove ( sta ) ;
list_add_tail ( & sta - > list , & tmp_list ) ;
}
write_unlock_bh ( & local - > sta_lock ) ;
list_for_each_entry_safe ( sta , tmp , & tmp_list , list ) {
sta_info_free ( sta ) ;
sta_info_put ( sta ) ;
}
2007-05-05 22:45:53 +04:00
}