@ -49,6 +49,7 @@
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <linux/pm_runtime.h>
# include <net/tso.h>
# include "iwl-debug.h"
# include "iwl-csr.h"
@ -226,6 +227,143 @@ static int iwl_pcie_gen2_set_tb(struct iwl_trans *trans,
return idx ;
}
static int iwl_pcie_gen2_build_amsdu ( struct iwl_trans * trans ,
struct sk_buff * skb ,
struct iwl_tfh_tfd * tfd , int start_len ,
u8 hdr_len , struct iwl_device_cmd * dev_cmd )
{
# ifdef CONFIG_INET
struct iwl_trans_pcie * trans_pcie = IWL_TRANS_GET_PCIE_TRANS ( trans ) ;
struct iwl_tx_cmd * tx_cmd = ( void * ) dev_cmd - > payload ;
struct ieee80211_hdr * hdr = ( void * ) skb - > data ;
unsigned int snap_ip_tcp_hdrlen , ip_hdrlen , total_len , hdr_room ;
unsigned int mss = skb_shinfo ( skb ) - > gso_size ;
u16 length , iv_len , amsdu_pad ;
u8 * start_hdr ;
struct iwl_tso_hdr_page * hdr_page ;
struct page * * page_ptr ;
struct tso_t tso ;
/* if the packet is protected, then it must be CCMP or GCMP */
iv_len = ieee80211_has_protected ( hdr - > frame_control ) ?
IEEE80211_CCMP_HDR_LEN : 0 ;
trace_iwlwifi_dev_tx ( trans - > dev , skb , tfd , sizeof ( * tfd ) ,
& dev_cmd - > hdr , start_len , NULL , 0 ) ;
ip_hdrlen = skb_transport_header ( skb ) - skb_network_header ( skb ) ;
snap_ip_tcp_hdrlen = 8 + ip_hdrlen + tcp_hdrlen ( skb ) ;
total_len = skb - > len - snap_ip_tcp_hdrlen - hdr_len - iv_len ;
amsdu_pad = 0 ;
/* total amount of header we may need for this A-MSDU */
hdr_room = DIV_ROUND_UP ( total_len , mss ) *
( 3 + snap_ip_tcp_hdrlen + sizeof ( struct ethhdr ) ) + iv_len ;
/* Our device supports 9 segments at most, it will fit in 1 page */
hdr_page = get_page_hdr ( trans , hdr_room ) ;
if ( ! hdr_page )
return - ENOMEM ;
get_page ( hdr_page - > page ) ;
start_hdr = hdr_page - > pos ;
page_ptr = ( void * ) ( ( u8 * ) skb - > cb + trans_pcie - > page_offs ) ;
* page_ptr = hdr_page - > page ;
memcpy ( hdr_page - > pos , skb - > data + hdr_len , iv_len ) ;
hdr_page - > pos + = iv_len ;
/*
* Pull the ieee80211 header + IV to be able to use TSO core ,
* we will restore it for the tx_status flow .
*/
skb_pull ( skb , hdr_len + iv_len ) ;
/*
* Remove the length of all the headers that we don ' t actually
* have in the MPDU by themselves , but that we duplicate into
* all the different MSDUs inside the A - MSDU .
*/
le16_add_cpu ( & tx_cmd - > len , - snap_ip_tcp_hdrlen ) ;
tso_start ( skb , & tso ) ;
while ( total_len ) {
/* this is the data left for this subframe */
unsigned int data_left = min_t ( unsigned int , mss , total_len ) ;
struct sk_buff * csum_skb = NULL ;
unsigned int tb_len ;
dma_addr_t tb_phys ;
struct tcphdr * tcph ;
u8 * iph , * subf_hdrs_start = hdr_page - > pos ;
total_len - = data_left ;
memset ( hdr_page - > pos , 0 , amsdu_pad ) ;
hdr_page - > pos + = amsdu_pad ;
amsdu_pad = ( 4 - ( sizeof ( struct ethhdr ) + snap_ip_tcp_hdrlen +
data_left ) ) & 0x3 ;
ether_addr_copy ( hdr_page - > pos , ieee80211_get_DA ( hdr ) ) ;
hdr_page - > pos + = ETH_ALEN ;
ether_addr_copy ( hdr_page - > pos , ieee80211_get_SA ( hdr ) ) ;
hdr_page - > pos + = ETH_ALEN ;
length = snap_ip_tcp_hdrlen + data_left ;
* ( ( __be16 * ) hdr_page - > pos ) = cpu_to_be16 ( length ) ;
hdr_page - > pos + = sizeof ( length ) ;
/*
* This will copy the SNAP as well which will be considered
* as MAC header .
*/
tso_build_hdr ( skb , hdr_page - > pos , & tso , data_left , ! total_len ) ;
iph = hdr_page - > pos + 8 ;
tcph = ( void * ) ( iph + ip_hdrlen ) ;
hdr_page - > pos + = snap_ip_tcp_hdrlen ;
tb_len = hdr_page - > pos - start_hdr ;
tb_phys = dma_map_single ( trans - > dev , start_hdr ,
tb_len , DMA_TO_DEVICE ) ;
if ( unlikely ( dma_mapping_error ( trans - > dev , tb_phys ) ) ) {
dev_kfree_skb ( csum_skb ) ;
goto out_err ;
}
iwl_pcie_gen2_set_tb ( trans , tfd , tb_phys , tb_len ) ;
trace_iwlwifi_dev_tx_tso_chunk ( trans - > dev , start_hdr , tb_len ) ;
/* add this subframe's headers' length to the tx_cmd */
le16_add_cpu ( & tx_cmd - > len , hdr_page - > pos - subf_hdrs_start ) ;
/* prepare the start_hdr for the next subframe */
start_hdr = hdr_page - > pos ;
/* put the payload */
while ( data_left ) {
tb_len = min_t ( unsigned int , tso . size , data_left ) ;
tb_phys = dma_map_single ( trans - > dev , tso . data ,
tb_len , DMA_TO_DEVICE ) ;
if ( unlikely ( dma_mapping_error ( trans - > dev , tb_phys ) ) ) {
dev_kfree_skb ( csum_skb ) ;
goto out_err ;
}
iwl_pcie_gen2_set_tb ( trans , tfd , tb_phys , tb_len ) ;
trace_iwlwifi_dev_tx_tso_chunk ( trans - > dev , tso . data ,
tb_len ) ;
data_left - = tb_len ;
tso_build_data ( skb , & tso , tb_len ) ;
}
}
/* re -add the WiFi header and IV */
skb_push ( skb , hdr_len + iv_len ) ;
return 0 ;
out_err :
# endif
return - EINVAL ;
}
static
struct iwl_tfh_tfd * iwl_pcie_gen2_build_tfd ( struct iwl_trans * trans ,
struct iwl_txq * txq ,
@ -238,15 +376,21 @@ struct iwl_tfh_tfd *iwl_pcie_gen2_build_tfd(struct iwl_trans *trans,
struct iwl_tfh_tfd * tfd =
iwl_pcie_get_tfd ( trans_pcie , txq , txq - > write_ptr ) ;
dma_addr_t tb_phys ;
bool amsdu ;
int i , len , tb1_len , tb2_len , hdr_len ;
void * tb1_addr ;
memset ( tfd , 0 , sizeof ( * tfd ) ) ;
amsdu = ieee80211_is_data_qos ( hdr - > frame_control ) & &
( * ieee80211_get_qos_ctl ( hdr ) &
IEEE80211_QOS_CTL_A_MSDU_PRESENT ) ;
tb_phys = iwl_pcie_get_first_tb_dma ( txq , txq - > write_ptr ) ;
/* The first TB points to bi-directional DMA data */
memcpy ( & txq - > first_tb_bufs [ txq - > write_ptr ] , & dev_cmd - > hdr ,
IWL_FIRST_TB_SIZE ) ;
if ( ! amsdu )
memcpy ( & txq - > first_tb_bufs [ txq - > write_ptr ] , & dev_cmd - > hdr ,
IWL_FIRST_TB_SIZE ) ;
iwl_pcie_gen2_set_tb ( trans , tfd , tb_phys , IWL_FIRST_TB_SIZE ) ;
@ -262,7 +406,11 @@ struct iwl_tfh_tfd *iwl_pcie_gen2_build_tfd(struct iwl_trans *trans,
len = sizeof ( struct iwl_tx_cmd_gen2 ) + sizeof ( struct iwl_cmd_header ) +
ieee80211_hdrlen ( hdr - > frame_control ) - IWL_FIRST_TB_SIZE ;
tb1_len = ALIGN ( len , 4 ) ;
/* do not align A-MSDU to dword as the subframe header aligns it */
if ( amsdu )
tb1_len = len ;
else
tb1_len = ALIGN ( len , 4 ) ;
/* map the data for TB1 */
tb1_addr = ( ( u8 * ) & dev_cmd - > hdr ) + IWL_FIRST_TB_SIZE ;
@ -271,8 +419,24 @@ struct iwl_tfh_tfd *iwl_pcie_gen2_build_tfd(struct iwl_trans *trans,
goto out_err ;
iwl_pcie_gen2_set_tb ( trans , tfd , tb_phys , tb1_len ) ;
/* set up TFD's third entry to point to remainder of skb's head */
hdr_len = ieee80211_hdrlen ( hdr - > frame_control ) ;
if ( amsdu ) {
if ( ! iwl_pcie_gen2_build_amsdu ( trans , skb , tfd ,
tb1_len + IWL_FIRST_TB_SIZE ,
hdr_len , dev_cmd ) )
goto out_err ;
/*
* building the A - MSDU might have changed this data , so memcpy
* it now
*/
memcpy ( & txq - > first_tb_bufs [ txq - > write_ptr ] , & dev_cmd - > hdr ,
IWL_FIRST_TB_SIZE ) ;
return tfd ;
}
/* set up TFD's third entry to point to remainder of skb's head */
tb2_len = skb_headlen ( skb ) - hdr_len ;
if ( tb2_len > 0 ) {