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"
2008-02-23 17:17:11 +03:00
# include "mesh.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
}
2008-02-21 16:09:30 +03:00
/* must hold local->sta_lock */
static struct sta_info * __sta_info_find ( struct ieee80211_local * local ,
u8 * addr )
2007-05-05 22:45:53 +04:00
{
struct sta_info * sta ;
sta = local - > sta_hash [ STA_HASH ( addr ) ] ;
while ( sta ) {
2008-02-21 16:09:30 +03:00
if ( compare_ether_addr ( sta - > addr , addr ) = = 0 )
2007-05-05 22:45:53 +04:00
break ;
sta = sta - > hnext ;
}
2008-02-21 16:09:30 +03:00
return sta ;
}
struct sta_info * sta_info_get ( struct ieee80211_local * local , u8 * addr )
{
struct sta_info * sta ;
read_lock_bh ( & local - > sta_lock ) ;
sta = __sta_info_find ( local , addr ) ;
if ( sta )
__sta_info_get ( sta ) ;
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 ) ;
2008-02-23 17:17:11 +03:00
struct sta_info * sta_info_get_by_idx ( struct ieee80211_local * local , int idx ,
struct net_device * dev )
{
struct sta_info * sta ;
int i = 0 ;
read_lock_bh ( & local - > sta_lock ) ;
list_for_each_entry ( sta , & local - > sta_list , list ) {
if ( i < idx ) {
+ + i ;
continue ;
} else if ( ! dev | | dev = = sta - > dev ) {
__sta_info_get ( sta ) ;
read_unlock_bh ( & local - > sta_lock ) ;
return sta ;
}
}
read_unlock_bh ( & local - > sta_lock ) ;
return NULL ;
}
2007-05-05 22:45:53 +04:00
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 ;
2007-12-25 18:00:33 +03:00
int i ;
2007-05-05 22:45:53 +04:00
/* 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 ) ;
}
2008-01-28 15:07:19 +03:00
for ( i = 0 ; i < STA_TID_NUM ; i + + ) {
2007-12-25 18:00:33 +03:00
del_timer_sync ( & sta - > ampdu_mlme . tid_rx [ i ] . session_timer ) ;
2008-01-28 15:07:19 +03:00
del_timer_sync ( & sta - > ampdu_mlme . tid_tx [ i ] . addba_resp_timer ) ;
}
2007-05-05 22:45:53 +04:00
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 ) ;
2008-02-21 16:09:30 +03:00
struct sta_info * sta_info_add ( struct ieee80211_local * local ,
struct net_device * dev , u8 * addr , gfp_t gfp )
2007-05-05 22:45:53 +04:00
{
struct sta_info * sta ;
2007-12-25 18:00:34 +03:00
int i ;
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 )
2008-02-21 16:09:30 +03:00
return ERR_PTR ( - ENOMEM ) ;
2007-05-05 22:45:53 +04:00
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 ) ;
2008-02-21 16:09:30 +03:00
return ERR_PTR ( - ENOMEM ) ;
2007-05-05 22:45:53 +04:00
}
memcpy ( sta - > addr , addr , ETH_ALEN ) ;
sta - > local = local ;
sta - > dev = dev ;
2007-12-25 18:00:34 +03:00
spin_lock_init ( & sta - > ampdu_mlme . ampdu_rx ) ;
2008-01-28 15:07:19 +03:00
spin_lock_init ( & sta - > ampdu_mlme . ampdu_tx ) ;
2007-12-25 18:00:34 +03:00
for ( i = 0 ; i < STA_TID_NUM ; i + + ) {
/* timer_to_tid must be initialized with identity mapping to
* enable session_timer ' s data differentiation . refer to
* sta_rx_agg_session_timer_expired for useage */
sta - > timer_to_tid [ i ] = i ;
2008-01-28 15:07:19 +03:00
/* tid to tx queue: initialize according to HW (0 is valid) */
sta - > tid_to_tx_q [ i ] = local - > hw . queues ;
2007-12-25 18:00:34 +03:00
/* rx timers */
sta - > ampdu_mlme . tid_rx [ i ] . session_timer . function =
sta_rx_agg_session_timer_expired ;
sta - > ampdu_mlme . tid_rx [ i ] . session_timer . data =
( unsigned long ) & sta - > timer_to_tid [ i ] ;
init_timer ( & sta - > ampdu_mlme . tid_rx [ i ] . session_timer ) ;
2008-01-28 15:07:19 +03:00
/* tx timers */
sta - > ampdu_mlme . tid_tx [ i ] . addba_resp_timer . function =
sta_addba_resp_timer_expired ;
sta - > ampdu_mlme . tid_tx [ i ] . addba_resp_timer . data =
( unsigned long ) & sta - > timer_to_tid [ i ] ;
init_timer ( & sta - > ampdu_mlme . tid_tx [ i ] . addba_resp_timer ) ;
2007-12-25 18:00:34 +03:00
}
2007-05-05 22:45:53 +04:00
skb_queue_head_init ( & sta - > ps_tx_buf ) ;
skb_queue_head_init ( & sta - > tx_filtered ) ;
2007-07-27 17:43:23 +04:00
write_lock_bh ( & local - > sta_lock ) ;
2008-02-21 16:09:30 +03:00
/* mark sta as used (by caller) */
__sta_info_get ( sta ) ;
/* check if STA exists already */
if ( __sta_info_find ( local , addr ) ) {
write_unlock_bh ( & local - > sta_lock ) ;
sta_info_put ( sta ) ;
return ERR_PTR ( - EEXIST ) ;
}
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-12-19 03:31:26 +03:00
if ( local - > ops - > sta_notify ) {
struct ieee80211_sub_if_data * sdata ;
sdata = IEEE80211_DEV_TO_SUB_IF ( dev ) ;
2007-12-19 03:31:27 +03:00
if ( sdata - > vif . type = = IEEE80211_IF_TYPE_VLAN )
2007-12-19 03:31:26 +03:00
sdata = sdata - > u . vlan . ap ;
local - > ops - > sta_notify ( local_to_hw ( local ) , & sdata - > vif ,
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 ;
}
2008-02-20 13:21:35 +03:00
static inline void __bss_tim_set ( struct ieee80211_if_ap * bss , u16 aid )
{
/*
* This format has been mandated by the IEEE specifications ,
* so this line may not be changed to use the __set_bit ( ) format .
*/
bss - > tim [ aid / 8 ] | = ( 1 < < ( aid % 8 ) ) ;
}
static inline void __bss_tim_clear ( struct ieee80211_if_ap * bss , u16 aid )
{
/*
* This format has been mandated by the IEEE specifications ,
* so this line may not be changed to use the __clear_bit ( ) format .
*/
bss - > tim [ aid / 8 ] & = ~ ( 1 < < ( aid % 8 ) ) ;
}
static void __sta_info_set_tim_bit ( struct ieee80211_if_ap * bss ,
struct sta_info * sta )
{
if ( bss )
__bss_tim_set ( bss , sta - > aid ) ;
if ( sta - > local - > ops - > set_tim )
sta - > local - > ops - > set_tim ( local_to_hw ( sta - > local ) , sta - > aid , 1 ) ;
}
void sta_info_set_tim_bit ( struct sta_info * sta )
{
struct ieee80211_sub_if_data * sdata ;
sdata = IEEE80211_DEV_TO_SUB_IF ( sta - > dev ) ;
read_lock_bh ( & sta - > local - > sta_lock ) ;
__sta_info_set_tim_bit ( sdata - > bss , sta ) ;
read_unlock_bh ( & sta - > local - > sta_lock ) ;
}
static void __sta_info_clear_tim_bit ( struct ieee80211_if_ap * bss ,
struct sta_info * sta )
{
if ( bss )
__bss_tim_clear ( bss , sta - > aid ) ;
if ( sta - > local - > ops - > set_tim )
sta - > local - > ops - > set_tim ( local_to_hw ( sta - > local ) , sta - > aid , 0 ) ;
}
void sta_info_clear_tim_bit ( struct sta_info * sta )
{
struct ieee80211_sub_if_data * sdata ;
sdata = IEEE80211_DEV_TO_SUB_IF ( sta - > dev ) ;
read_lock_bh ( & sta - > local - > sta_lock ) ;
__sta_info_clear_tim_bit ( sdata - > bss , sta ) ;
read_unlock_bh ( & sta - > local - > sta_lock ) ;
}
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 ) ;
2008-02-20 13:21:35 +03:00
__sta_info_clear_tim_bit ( sdata - > bss , sta ) ;
2007-05-05 22:45:53 +04:00
}
local - > num_sta - - ;
2008-02-23 17:17:11 +03:00
2008-02-23 17:17:19 +03:00
if ( ieee80211_vif_is_mesh ( & sdata - > vif ) )
2008-02-23 17:17:11 +03:00
mesh_accept_plinks_update ( sdata - > dev ) ;
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 ;
2008-02-23 17:17:11 +03:00
struct ieee80211_sub_if_data * sdata = IEEE80211_DEV_TO_SUB_IF ( sta - > dev ) ;
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
2008-02-23 17:17:19 +03:00
if ( ieee80211_vif_is_mesh ( & sdata - > vif ) )
2008-02-23 17:17:11 +03:00
mesh_plink_deactivate ( sta ) ;
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 ) ;
2008-02-25 18:27:45 +03:00
WARN_ON ( sta - > key ) ;
2007-07-27 17:43:23 +04:00
2007-12-19 03:31:26 +03:00
if ( local - > ops - > sta_notify ) {
2007-12-19 03:31:27 +03:00
if ( sdata - > vif . type = = IEEE80211_IF_TYPE_VLAN )
2007-12-19 03:31:26 +03:00
sdata = sdata - > u . vlan . ap ;
local - > ops - > sta_notify ( local_to_hw ( local ) , & sdata - > vif ,
STA_NOTIFY_REMOVE , sta - > addr ) ;
}
2007-09-30 15:52:37 +04:00
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 ;
2008-02-20 04:07:21 +03:00
struct ieee80211_sub_if_data * sdata ;
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 ) ;
2008-02-20 04:07:21 +03:00
if ( sta_info_buffer_expired ( local , sta , skb ) )
2007-05-05 22:45:53 +04:00
skb = __skb_dequeue ( & sta - > ps_tx_buf ) ;
2008-02-20 04:07:21 +03:00
else
2007-05-05 22:45:53 +04:00
skb = NULL ;
spin_unlock_irqrestore ( & sta - > ps_tx_buf . lock , flags ) ;
2008-02-20 04:07:21 +03:00
if ( ! skb )
2007-05-05 22:45:53 +04:00
break ;
2008-02-20 04:07:21 +03:00
sdata = IEEE80211_DEV_TO_SUB_IF ( sta - > dev ) ;
local - > total_ps_buffered - - ;
printk ( KERN_DEBUG " Buffered frame expired (STA "
" %s) \n " , print_mac ( mac , sta - > addr ) ) ;
dev_kfree_skb ( skb ) ;
2008-02-20 13:21:35 +03:00
if ( skb_queue_empty ( & sta - > ps_tx_buf ) )
sta_info_clear_tim_bit ( sta ) ;
2007-05-05 22:45:53 +04:00
}
}
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
}
/**
* 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
}