2009-11-18 20:42:47 +03:00
/*
* Copyright 2002 - 2005 , Instant802 Networks , Inc .
* Copyright 2005 - 2006 , Devicescape Software , Inc .
* Copyright 2006 - 2007 Jiri Benc < jbenc @ suse . cz >
2010-02-15 13:53:10 +03:00
* Copyright 2008 - 2010 Johannes Berg < johannes @ sipsolutions . net >
2009-11-18 20:42:47 +03:00
*
* 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 <net/mac80211.h>
# include "ieee80211_i.h"
# include "rate.h"
# include "mesh.h"
# include "led.h"
void ieee80211_tx_status_irqsafe ( struct ieee80211_hw * hw ,
struct sk_buff * skb )
{
struct ieee80211_local * local = hw_to_local ( hw ) ;
struct ieee80211_tx_info * info = IEEE80211_SKB_CB ( skb ) ;
int tmp ;
skb - > pkt_type = IEEE80211_TX_STATUS_MSG ;
skb_queue_tail ( info - > flags & IEEE80211_TX_CTL_REQ_TX_STATUS ?
& local - > skb_queue : & local - > skb_queue_unreliable , skb ) ;
tmp = skb_queue_len ( & local - > skb_queue ) +
skb_queue_len ( & local - > skb_queue_unreliable ) ;
while ( tmp > IEEE80211_IRQSAFE_QUEUE_LIMIT & &
( skb = skb_dequeue ( & local - > skb_queue_unreliable ) ) ) {
dev_kfree_skb_irq ( skb ) ;
tmp - - ;
I802_DEBUG_INC ( local - > tx_status_drop ) ;
}
tasklet_schedule ( & local - > tasklet ) ;
}
EXPORT_SYMBOL ( ieee80211_tx_status_irqsafe ) ;
static void ieee80211_handle_filtered_frame ( struct ieee80211_local * local ,
struct sta_info * sta ,
struct sk_buff * skb )
{
struct ieee80211_tx_info * info = IEEE80211_SKB_CB ( skb ) ;
2010-01-17 03:47:56 +03:00
/*
* This skb ' survived ' a round - trip through the driver , and
* hopefully the driver didn ' t mangle it too badly . However ,
2010-05-18 15:44:54 +04:00
* we can definitely not rely on the control information
2010-01-17 03:47:59 +03:00
* being correct . Clear it so we don ' t get junk there , and
* indicate that it needs new processing , but must not be
* modified / encrypted again .
2010-01-17 03:47:56 +03:00
*/
memset ( & info - > control , 0 , sizeof ( info - > control ) ) ;
2010-01-25 21:07:39 +03:00
info - > control . jiffies = jiffies ;
info - > control . vif = & sta - > sdata - > vif ;
2010-01-17 03:47:59 +03:00
info - > flags | = IEEE80211_TX_INTFL_NEED_TXPROCESSING |
IEEE80211_TX_INTFL_RETRANSMISSION ;
2010-09-21 23:36:18 +04:00
info - > flags & = ~ IEEE80211_TX_TEMPORARY_FLAGS ;
2010-01-17 03:47:56 +03:00
2009-11-18 20:42:47 +03:00
sta - > tx_filtered_count + + ;
/*
* Clear the TX filter mask for this STA when sending the next
* packet . If the STA went to power save mode , this will happen
* when it wakes up for the next time .
*/
set_sta_flags ( sta , WLAN_STA_CLEAR_PS_FILT ) ;
/*
* This code races in the following way :
*
* ( 1 ) STA sends frame indicating it will go to sleep and does so
* ( 2 ) hardware / firmware adds STA to filter list , passes frame up
* ( 3 ) hardware / firmware processes TX fifo and suppresses a frame
* ( 4 ) we get TX status before having processed the frame and
* knowing that the STA has gone to sleep .
*
* This is actually quite unlikely even when both those events are
* processed from interrupts coming in quickly after one another or
* even at the same time because we queue both TX status events and
* RX frames to be processed by a tasklet and process them in the
* same order that they were received or TX status last . Hence , there
* is no race as long as the frame RX is processed before the next TX
* status , which drivers can ensure , see below .
*
* Note that this can only happen if the hardware or firmware can
* actually add STAs to the filter list , if this is done by the
* driver in response to set_tim ( ) ( which will only reduce the race
* this whole filtering tries to solve , not completely solve it )
* this situation cannot happen .
*
* To completely solve this race drivers need to make sure that they
* ( a ) don ' t mix the irq - safe / not irq - safe TX status / RX processing
* functions and
* ( b ) always process RX events before TX status events if ordering
* can be unknown , for example with different interrupt status
* bits .
2011-01-31 23:29:13 +03:00
* ( c ) if PS mode transitions are manual ( i . e . the flag
* % IEEE80211_HW_AP_LINK_PS is set ) , always process PS state
* changes before calling TX status events if ordering can be
* unknown .
2009-11-18 20:42:47 +03:00
*/
if ( test_sta_flags ( sta , WLAN_STA_PS_STA ) & &
skb_queue_len ( & sta - > tx_filtered ) < STA_MAX_TX_BUFFER ) {
skb_queue_tail ( & sta - > tx_filtered , skb ) ;
2011-09-29 18:04:27 +04:00
sta_info_recalc_tim ( sta ) ;
2009-11-18 20:42:47 +03:00
return ;
}
if ( ! test_sta_flags ( sta , WLAN_STA_PS_STA ) & &
! ( info - > flags & IEEE80211_TX_INTFL_RETRIED ) ) {
/* Software retry the packet once */
info - > flags | = IEEE80211_TX_INTFL_RETRIED ;
ieee80211_add_pending_skb ( local , skb ) ;
return ;
}
# ifdef CONFIG_MAC80211_VERBOSE_DEBUG
if ( net_ratelimit ( ) )
2010-08-21 03:25:38 +04:00
wiphy_debug ( local - > hw . wiphy ,
" dropped TX filtered frame, queue_len=%d PS=%d @%lu \n " ,
skb_queue_len ( & sta - > tx_filtered ) ,
! ! test_sta_flags ( sta , WLAN_STA_PS_STA ) , jiffies ) ;
2009-11-18 20:42:47 +03:00
# endif
dev_kfree_skb ( skb ) ;
}
2011-08-28 23:11:01 +04:00
static void ieee80211_check_pending_bar ( struct sta_info * sta , u8 * addr , u8 tid )
{
struct tid_ampdu_tx * tid_tx ;
tid_tx = rcu_dereference ( sta - > ampdu_mlme . tid_tx [ tid ] ) ;
if ( ! tid_tx | | ! tid_tx - > bar_pending )
return ;
tid_tx - > bar_pending = false ;
2011-08-20 17:53:55 +04:00
ieee80211_send_bar ( & sta - > sdata - > vif , addr , tid , tid_tx - > failed_bar_ssn ) ;
2011-08-28 23:11:01 +04:00
}
2009-12-01 15:37:02 +03:00
static void ieee80211_frame_acked ( struct sta_info * sta , struct sk_buff * skb )
{
struct ieee80211_mgmt * mgmt = ( void * ) skb - > data ;
struct ieee80211_local * local = sta - > local ;
struct ieee80211_sub_if_data * sdata = sta - > sdata ;
2011-08-28 23:11:01 +04:00
if ( ieee80211_is_data_qos ( mgmt - > frame_control ) ) {
struct ieee80211_hdr * hdr = ( void * ) skb - > data ;
u8 * qc = ieee80211_get_qos_ctl ( hdr ) ;
u16 tid = qc [ 0 ] & 0xf ;
ieee80211_check_pending_bar ( sta , hdr - > addr1 , tid ) ;
}
2009-12-01 15:37:02 +03:00
if ( ieee80211_is_action ( mgmt - > frame_control ) & &
sdata - > vif . type = = NL80211_IFTYPE_STATION & &
mgmt - > u . action . category = = WLAN_CATEGORY_HT & &
mgmt - > u . action . u . ht_smps . action = = WLAN_HT_ACTION_SMPS ) {
/*
* This update looks racy , but isn ' t - - if we come
* here we ' ve definitely got a station that we ' re
* talking to , and on a managed interface that can
* only be the AP . And the only other place updating
* this variable is before we ' re associated .
*/
switch ( mgmt - > u . action . u . ht_smps . smps_control ) {
case WLAN_HT_SMPS_CONTROL_DYNAMIC :
sta - > sdata - > u . mgd . ap_smps = IEEE80211_SMPS_DYNAMIC ;
break ;
case WLAN_HT_SMPS_CONTROL_STATIC :
sta - > sdata - > u . mgd . ap_smps = IEEE80211_SMPS_STATIC ;
break ;
case WLAN_HT_SMPS_CONTROL_DISABLED :
default : /* shouldn't happen since we don't send that */
sta - > sdata - > u . mgd . ap_smps = IEEE80211_SMPS_OFF ;
break ;
}
ieee80211_queue_work ( & local - > hw , & local - > recalc_smps ) ;
}
}
2011-08-28 23:11:01 +04:00
static void ieee80211_set_bar_pending ( struct sta_info * sta , u8 tid , u16 ssn )
{
struct tid_ampdu_tx * tid_tx ;
tid_tx = rcu_dereference ( sta - > ampdu_mlme . tid_tx [ tid ] ) ;
if ( ! tid_tx )
return ;
tid_tx - > failed_bar_ssn = ssn ;
tid_tx - > bar_pending = true ;
}
2010-11-24 10:10:06 +03:00
/*
* Use a static threshold for now , best value to be determined
* by testing . . .
* Should it depend on :
* - on # of retransmissions
* - current throughput ( higher value for higher tpt ) ?
*/
# define STA_LOST_PKT_THRESHOLD 50
2009-11-18 20:42:47 +03:00
void ieee80211_tx_status ( struct ieee80211_hw * hw , struct sk_buff * skb )
{
struct sk_buff * skb2 ;
struct ieee80211_hdr * hdr = ( struct ieee80211_hdr * ) skb - > data ;
struct ieee80211_local * local = hw_to_local ( hw ) ;
struct ieee80211_tx_info * info = IEEE80211_SKB_CB ( skb ) ;
u16 frag , type ;
__le16 fc ;
struct ieee80211_supported_band * sband ;
struct ieee80211_tx_status_rtap_hdr * rthdr ;
struct ieee80211_sub_if_data * sdata ;
struct net_device * prev_dev = NULL ;
2009-11-25 19:46:18 +03:00
struct sta_info * sta , * tmp ;
2009-11-18 20:42:47 +03:00
int retry_count = - 1 , i ;
2010-04-22 11:27:48 +04:00
int rates_idx = - 1 ;
2010-03-11 18:28:24 +03:00
bool send_to_cooked ;
2010-12-02 23:01:08 +03:00
bool acked ;
2011-08-11 18:17:42 +04:00
struct ieee80211_bar * bar ;
u16 tid ;
2009-11-18 20:42:47 +03:00
for ( i = 0 ; i < IEEE80211_TX_MAX_RATES ; i + + ) {
2011-03-21 17:07:55 +03:00
if ( info - > status . rates [ i ] . idx < 0 ) {
break ;
} else if ( i > = hw - > max_report_rates ) {
/* the HW cannot have attempted that rate */
2009-11-18 20:42:47 +03:00
info - > status . rates [ i ] . idx = - 1 ;
info - > status . rates [ i ] . count = 0 ;
2011-03-21 17:07:55 +03:00
break ;
2009-11-18 20:42:47 +03:00
}
retry_count + = info - > status . rates [ i ] . count ;
}
2011-03-21 17:07:55 +03:00
rates_idx = i - 1 ;
2009-11-18 20:42:47 +03:00
if ( retry_count < 0 )
retry_count = 0 ;
rcu_read_lock ( ) ;
sband = local - > hw . wiphy - > bands [ info - > band ] ;
2010-02-09 12:20:28 +03:00
fc = hdr - > frame_control ;
2009-11-18 20:42:47 +03:00
2009-11-25 19:46:18 +03:00
for_each_sta_info ( local , hdr - > addr1 , sta , tmp ) {
/* skip wrong virtual interface */
2009-11-25 19:46:19 +03:00
if ( memcmp ( hdr - > addr2 , sta - > sdata - > vif . addr , ETH_ALEN ) )
2009-11-25 19:46:18 +03:00
continue ;
2009-11-18 20:42:47 +03:00
2010-12-02 23:01:08 +03:00
acked = ! ! ( info - > flags & IEEE80211_TX_STAT_ACK ) ;
if ( ! acked & & test_sta_flags ( sta , WLAN_STA_PS_STA ) ) {
2009-11-18 20:42:47 +03:00
/*
* The STA is in power save mode , so assume
* that this TX packet failed because of that .
*/
ieee80211_handle_filtered_frame ( local , sta , skb ) ;
rcu_read_unlock ( ) ;
return ;
}
2010-04-22 11:27:48 +04:00
if ( ( local - > hw . flags & IEEE80211_HW_HAS_RATE_CONTROL ) & &
( rates_idx ! = - 1 ) )
sta - > last_tx_rate = info - > status . rates [ rates_idx ] ;
2009-11-18 20:42:47 +03:00
if ( ( info - > flags & IEEE80211_TX_STAT_AMPDU_NO_BACK ) & &
( ieee80211_is_data_qos ( fc ) ) ) {
u16 tid , ssn ;
u8 * qc ;
qc = ieee80211_get_qos_ctl ( hdr ) ;
tid = qc [ 0 ] & 0xf ;
ssn = ( ( le16_to_cpu ( hdr - > seq_ctrl ) + 0x10 )
& IEEE80211_SCTL_SEQ ) ;
2011-08-20 17:53:55 +04:00
ieee80211_send_bar ( & sta - > sdata - > vif , hdr - > addr1 ,
2009-11-18 20:42:47 +03:00
tid , ssn ) ;
}
2011-08-11 18:17:42 +04:00
if ( ! acked & & ieee80211_is_back_req ( fc ) ) {
2011-09-15 11:37:46 +04:00
u16 control ;
2011-08-11 18:17:42 +04:00
/*
2011-09-15 11:37:46 +04:00
* BAR failed , store the last SSN and retry sending
* the BAR when the next unicast transmission on the
* same TID succeeds .
2011-08-11 18:17:42 +04:00
*/
bar = ( struct ieee80211_bar * ) skb - > data ;
2011-09-15 11:37:46 +04:00
control = le16_to_cpu ( bar - > control ) ;
if ( ! ( control & IEEE80211_BAR_CTRL_MULTI_TID ) ) {
2011-08-28 23:11:01 +04:00
u16 ssn = le16_to_cpu ( bar - > start_seq_num ) ;
2011-09-15 11:37:46 +04:00
tid = ( control &
2011-08-11 18:17:42 +04:00
IEEE80211_BAR_CTRL_TID_INFO_MASK ) > >
IEEE80211_BAR_CTRL_TID_INFO_SHIFT ;
2011-08-28 23:11:01 +04:00
ieee80211_set_bar_pending ( sta , tid , ssn ) ;
2011-08-11 18:17:42 +04:00
}
}
2009-11-18 20:42:47 +03:00
if ( info - > flags & IEEE80211_TX_STAT_TX_FILTERED ) {
ieee80211_handle_filtered_frame ( local , sta , skb ) ;
rcu_read_unlock ( ) ;
return ;
} else {
2010-12-02 23:01:08 +03:00
if ( ! acked )
2009-11-18 20:42:47 +03:00
sta - > tx_retry_failed + + ;
sta - > tx_retry_count + = retry_count ;
}
rate_control_tx_status ( local , sband , sta , skb ) ;
if ( ieee80211_vif_is_mesh ( & sta - > sdata - > vif ) )
ieee80211s_update_metric ( local , sta , skb ) ;
2009-12-01 15:37:02 +03:00
2010-12-02 23:01:08 +03:00
if ( ! ( info - > flags & IEEE80211_TX_CTL_INJECTED ) & & acked )
2009-12-01 15:37:02 +03:00
ieee80211_frame_acked ( sta , skb ) ;
2010-11-24 10:10:06 +03:00
2010-12-02 23:01:08 +03:00
if ( ( sta - > sdata - > vif . type = = NL80211_IFTYPE_STATION ) & &
( local - > hw . flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS ) )
ieee80211_sta_tx_notify ( sta - > sdata , ( void * ) skb - > data , acked ) ;
2010-11-24 10:10:06 +03:00
if ( local - > hw . flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS ) {
if ( info - > flags & IEEE80211_TX_STAT_ACK ) {
if ( sta - > lost_packets )
sta - > lost_packets = 0 ;
} else if ( + + sta - > lost_packets > = STA_LOST_PKT_THRESHOLD ) {
cfg80211_cqm_pktloss_notify ( sta - > sdata - > dev ,
sta - > sta . addr ,
sta - > lost_packets ,
GFP_ATOMIC ) ;
sta - > lost_packets = 0 ;
}
}
2009-11-18 20:42:47 +03:00
}
rcu_read_unlock ( ) ;
ieee80211_led_tx ( local , 0 ) ;
/* SNMP counters
* Fragments are passed to low - level drivers as separate skbs , so these
* are actually fragments , not frames . Update frame counters only for
* the first fragment of the frame . */
frag = le16_to_cpu ( hdr - > seq_ctrl ) & IEEE80211_SCTL_FRAG ;
type = le16_to_cpu ( hdr - > frame_control ) & IEEE80211_FCTL_FTYPE ;
if ( info - > flags & IEEE80211_TX_STAT_ACK ) {
if ( frag = = 0 ) {
local - > dot11TransmittedFrameCount + + ;
if ( is_multicast_ether_addr ( hdr - > addr1 ) )
local - > dot11MulticastTransmittedFrameCount + + ;
if ( retry_count > 0 )
local - > dot11RetryCount + + ;
if ( retry_count > 1 )
local - > dot11MultipleRetryCount + + ;
}
/* This counter shall be incremented for an acknowledged MPDU
* with an individual address in the address 1 field or an MPDU
* with a multicast address in the address 1 field of type Data
* or Management . */
if ( ! is_multicast_ether_addr ( hdr - > addr1 ) | |
type = = IEEE80211_FTYPE_DATA | |
type = = IEEE80211_FTYPE_MGMT )
local - > dot11TransmittedFragmentCount + + ;
} else {
if ( frag = = 0 )
local - > dot11FailedCount + + ;
}
2010-02-09 12:20:28 +03:00
if ( ieee80211_is_nullfunc ( fc ) & & ieee80211_has_pm ( fc ) & &
( local - > hw . flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS ) & &
! ( info - > flags & IEEE80211_TX_CTL_INJECTED ) & &
local - > ps_sdata & & ! ( local - > scanning ) ) {
if ( info - > flags & IEEE80211_TX_STAT_ACK ) {
local - > ps_sdata - > u . mgd . flags | =
IEEE80211_STA_NULLFUNC_ACKED ;
} else
mod_timer ( & local - > dynamic_ps_timer , jiffies +
msecs_to_jiffies ( 10 ) ) ;
}
2010-11-25 12:02:30 +03:00
if ( info - > flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX ) {
struct ieee80211_work * wk ;
2011-02-02 18:58:06 +03:00
u64 cookie = ( unsigned long ) skb ;
2010-11-25 12:02:30 +03:00
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( wk , & local - > work_list , list ) {
if ( wk - > type ! = IEEE80211_WORK_OFFCHANNEL_TX )
continue ;
if ( wk - > offchan_tx . frame ! = skb )
continue ;
wk - > offchan_tx . frame = NULL ;
break ;
}
rcu_read_unlock ( ) ;
2011-02-02 18:58:06 +03:00
if ( local - > hw_roc_skb_for_status = = skb ) {
cookie = local - > hw_roc_cookie ^ 2 ;
local - > hw_roc_skb_for_status = NULL ;
}
2011-02-25 17:36:57 +03:00
2010-08-12 17:38:38 +04:00
cfg80211_mgmt_tx_status (
2011-02-02 18:58:06 +03:00
skb - > dev , cookie , skb - > data , skb - > len ,
2010-02-15 13:53:10 +03:00
! ! ( info - > flags & IEEE80211_TX_STAT_ACK ) , GFP_ATOMIC ) ;
2010-11-25 12:02:30 +03:00
}
2010-02-15 13:53:10 +03:00
2009-11-18 20:42:47 +03:00
/* this was a transmitted frame, but now we want to reuse it */
skb_orphan ( skb ) ;
2010-03-11 18:28:24 +03:00
/* Need to make a copy before skb->cb gets cleared */
send_to_cooked = ! ! ( info - > flags & IEEE80211_TX_CTL_INJECTED ) | |
( type ! = IEEE80211_FTYPE_DATA ) ;
2009-11-18 20:42:47 +03:00
/*
* This is a bit racy but we can avoid a lot of work
* with this test . . .
*/
2010-03-11 18:28:24 +03:00
if ( ! local - > monitors & & ( ! send_to_cooked | | ! local - > cooked_mntrs ) ) {
2009-11-18 20:42:47 +03:00
dev_kfree_skb ( skb ) ;
return ;
}
/* send frame to monitor interfaces now */
if ( skb_headroom ( skb ) < sizeof ( * rthdr ) ) {
printk ( KERN_ERR " ieee80211_tx_status: headroom too small \n " ) ;
dev_kfree_skb ( skb ) ;
return ;
}
rthdr = ( struct ieee80211_tx_status_rtap_hdr * )
skb_push ( skb , sizeof ( * rthdr ) ) ;
memset ( rthdr , 0 , sizeof ( * rthdr ) ) ;
rthdr - > hdr . it_len = cpu_to_le16 ( sizeof ( * rthdr ) ) ;
rthdr - > hdr . it_present =
cpu_to_le32 ( ( 1 < < IEEE80211_RADIOTAP_TX_FLAGS ) |
( 1 < < IEEE80211_RADIOTAP_DATA_RETRIES ) |
( 1 < < IEEE80211_RADIOTAP_RATE ) ) ;
if ( ! ( info - > flags & IEEE80211_TX_STAT_ACK ) & &
! is_multicast_ether_addr ( hdr - > addr1 ) )
rthdr - > tx_flags | = cpu_to_le16 ( IEEE80211_RADIOTAP_F_TX_FAIL ) ;
/*
* XXX : Once radiotap gets the bitmap reset thing the vendor
* extensions proposal contains , we can actually report
* the whole set of tries we did .
*/
if ( ( info - > status . rates [ 0 ] . flags & IEEE80211_TX_RC_USE_RTS_CTS ) | |
( info - > status . rates [ 0 ] . flags & IEEE80211_TX_RC_USE_CTS_PROTECT ) )
rthdr - > tx_flags | = cpu_to_le16 ( IEEE80211_RADIOTAP_F_TX_CTS ) ;
else if ( info - > status . rates [ 0 ] . flags & IEEE80211_TX_RC_USE_RTS_CTS )
rthdr - > tx_flags | = cpu_to_le16 ( IEEE80211_RADIOTAP_F_TX_RTS ) ;
if ( info - > status . rates [ 0 ] . idx > = 0 & &
! ( info - > status . rates [ 0 ] . flags & IEEE80211_TX_RC_MCS ) )
rthdr - > rate = sband - > bitrates [
info - > status . rates [ 0 ] . idx ] . bitrate / 5 ;
/* for now report the total retry_count */
rthdr - > data_retries = retry_count ;
/* XXX: is this sufficient for BPF? */
skb_set_mac_header ( skb , 0 ) ;
skb - > ip_summed = CHECKSUM_UNNECESSARY ;
skb - > pkt_type = PACKET_OTHERHOST ;
skb - > protocol = htons ( ETH_P_802_2 ) ;
memset ( skb - > cb , 0 , sizeof ( skb - > cb ) ) ;
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( sdata , & local - > interfaces , list ) {
if ( sdata - > vif . type = = NL80211_IFTYPE_MONITOR ) {
2009-12-23 15:15:31 +03:00
if ( ! ieee80211_sdata_running ( sdata ) )
2009-11-18 20:42:47 +03:00
continue ;
if ( ( sdata - > u . mntr_flags & MONITOR_FLAG_COOK_FRAMES ) & &
2010-03-11 18:28:24 +03:00
! send_to_cooked )
2009-11-18 20:42:47 +03:00
continue ;
if ( prev_dev ) {
skb2 = skb_clone ( skb , GFP_ATOMIC ) ;
if ( skb2 ) {
skb2 - > dev = prev_dev ;
2010-10-07 19:35:40 +04:00
netif_rx ( skb2 ) ;
2009-11-18 20:42:47 +03:00
}
}
prev_dev = sdata - > dev ;
}
}
if ( prev_dev ) {
skb - > dev = prev_dev ;
2010-10-07 19:35:40 +04:00
netif_rx ( skb ) ;
2009-11-18 20:42:47 +03:00
skb = NULL ;
}
rcu_read_unlock ( ) ;
dev_kfree_skb ( skb ) ;
}
EXPORT_SYMBOL ( ieee80211_tx_status ) ;
2011-04-18 15:22:28 +04:00
void ieee80211_report_low_ack ( struct ieee80211_sta * pubsta , u32 num_packets )
{
struct sta_info * sta = container_of ( pubsta , struct sta_info , sta ) ;
cfg80211_cqm_pktloss_notify ( sta - > sdata - > dev , sta - > sta . addr ,
num_packets , GFP_ATOMIC ) ;
}
EXPORT_SYMBOL ( ieee80211_report_low_ack ) ;