Commit 5e6e3a92 authored by Bing Zhao's avatar Bing Zhao Committed by John W. Linville
Browse files

wireless: mwifiex: initial commit for Marvell mwifiex driver



This driver adds WiFi support for Marvell 802.11n based chipsets
with SDIO interface. Currently only SD8787 is supported. More
chipsets will be supported later.

drivers/net/wireless/mwifiex/
Signed-off-by: default avatarNishant Sarmukadam <nishants@marvell.com>
Signed-off-by: default avatarAmitkumar Karwar <akarwar@marvell.com>
Signed-off-by: default avatarKiran Divekar <dkiran@marvell.com>
Signed-off-by: default avatarBing Zhao <bzhao@marvell.com>
Signed-off-by: default avatarYogesh Ashok Powar <yogeshp@marvell.com>
Signed-off-by: default avatarMarc Yang <yangyang@marvell.com>
Signed-off-by: default avatarRamesh Radhakrishnan <rramesh@marvell.com>
Signed-off-by: default avatarFrank Huang <frankh@marvell.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 903946e6
......@@ -284,5 +284,6 @@ source "drivers/net/wireless/rtlwifi/Kconfig"
source "drivers/net/wireless/wl1251/Kconfig"
source "drivers/net/wireless/wl12xx/Kconfig"
source "drivers/net/wireless/zd1211rw/Kconfig"
source "drivers/net/wireless/mwifiex/Kconfig"
endif # WLAN
......@@ -56,3 +56,5 @@ obj-$(CONFIG_WL12XX) += wl12xx/
obj-$(CONFIG_WL12XX_PLATFORM_DATA) += wl12xx/
obj-$(CONFIG_IWM) += iwmc3200wifi/
obj-$(CONFIG_MWIFIEX) += mwifiex/
This diff is collapsed.
/*
* Marvell Wireless LAN device driver: 802.11n
*
* Copyright (C) 2011, Marvell International Ltd.
*
* This software file (the "File") is distributed by Marvell International
* Ltd. under the terms of the GNU General Public License Version 2, June 1991
* (the "License"). You may use, redistribute and/or modify this File in
* accordance with the terms and conditions of the License, a copy of which
* is available by writing to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
* worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
* this warranty disclaimer.
*/
#ifndef _MWIFIEX_11N_H_
#define _MWIFIEX_11N_H_
#include "11n_aggr.h"
#include "11n_rxreorder.h"
#include "wmm.h"
void mwifiex_show_dot_11n_dev_cap(struct mwifiex_adapter *adapter, u32 cap);
void mwifiex_show_dev_mcs_support(struct mwifiex_adapter *adapter, u8 support);
int mwifiex_ret_11n_delba(struct mwifiex_private *priv,
struct host_cmd_ds_command *resp);
int mwifiex_ret_11n_addba_req(struct mwifiex_private *priv,
struct host_cmd_ds_command *resp);
int mwifiex_ret_11n_cfg(struct mwifiex_private *priv,
struct host_cmd_ds_command *resp,
void *data_buf);
int mwifiex_cmd_11n_cfg(struct mwifiex_private *priv,
struct host_cmd_ds_command *cmd,
u16 cmd_action, void *data_buf);
int mwifiex_cmd_11n_cfg(struct mwifiex_private *priv,
struct host_cmd_ds_command *cmd,
u16 cmd_action, void *data_buf);
int mwifiex_cmd_append_11n_tlv(struct mwifiex_private *priv,
struct mwifiex_bssdescriptor *bss_desc,
u8 **buffer);
void mwifiex_cfg_tx_buf(struct mwifiex_private *priv,
struct mwifiex_bssdescriptor *bss_desc);
void mwifiex_fill_cap_info(struct mwifiex_private *,
struct mwifiex_ie_types_htcap *);
int mwifiex_set_get_11n_htcap_cfg(struct mwifiex_private *priv,
u16 action, int *htcap_cfg);
void mwifiex_11n_delete_tx_ba_stream_tbl_entry(struct mwifiex_private *priv,
struct mwifiex_tx_ba_stream_tbl
*tx_tbl);
void mwifiex_11n_delete_all_tx_ba_stream_tbl(struct mwifiex_private *priv);
struct mwifiex_tx_ba_stream_tbl *mwifiex_11n_get_tx_ba_stream_tbl(struct
mwifiex_private
*priv, int tid,
u8 *ra);
void mwifiex_11n_create_tx_ba_stream_tbl(struct mwifiex_private *priv, u8 *ra,
int tid,
enum mwifiex_ba_status ba_status);
int mwifiex_send_addba(struct mwifiex_private *priv, int tid, u8 *peer_mac);
int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac,
int initiator);
void mwifiex_11n_delete_ba_stream(struct mwifiex_private *priv, u8 *del_ba);
int mwifiex_get_rx_reorder_tbl(struct mwifiex_private *priv,
struct mwifiex_ds_rx_reorder_tbl *buf);
int mwifiex_get_tx_ba_stream_tbl(struct mwifiex_private *priv,
struct mwifiex_ds_tx_ba_stream_tbl *buf);
int mwifiex_ret_amsdu_aggr_ctrl(struct mwifiex_private *priv,
struct host_cmd_ds_command
*resp,
void *data_buf);
int mwifiex_cmd_recfg_tx_buf(struct mwifiex_private *priv,
struct host_cmd_ds_command *cmd,
int cmd_action, void *data_buf);
int mwifiex_cmd_amsdu_aggr_ctrl(struct mwifiex_private *priv,
struct host_cmd_ds_command *cmd,
int cmd_action,
void *data_buf);
/*
* This function checks whether AMPDU is allowed or not for a particular TID.
*/
static inline u8
mwifiex_is_ampdu_allowed(struct mwifiex_private *priv,
struct mwifiex_ra_list_tbl *ptr, int tid)
{
return ((priv->aggr_prio_tbl[tid].ampdu_ap != BA_STREAM_NOT_ALLOWED)
? true : false);
}
/*
* This function checks whether AMSDU is allowed or not for a particular TID.
*/
static inline u8
mwifiex_is_amsdu_allowed(struct mwifiex_private *priv,
struct mwifiex_ra_list_tbl *ptr, int tid)
{
return (((priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED)
&& ((priv->is_data_rate_auto)
|| !((priv->bitmap_rates[2]) & 0x03)))
? true : false);
}
/*
* This function checks whether a BA stream is available or not.
*/
static inline u8
mwifiex_is_ba_stream_avail(struct mwifiex_private *priv)
{
struct mwifiex_private *pmpriv = NULL;
u8 i = 0;
u32 ba_stream_num = 0;
for (i = 0; i < priv->adapter->priv_num; i++) {
pmpriv = priv->adapter->priv[i];
if (pmpriv)
ba_stream_num +=
mwifiex_wmm_list_len(priv->adapter,
(struct list_head
*) &pmpriv->
tx_ba_stream_tbl_ptr);
}
return ((ba_stream_num <
MWIFIEX_MAX_TX_BASTREAM_SUPPORTED) ? true : false);
}
/*
* This function finds the correct Tx BA stream to delete.
*
* Upon successfully locating, both the TID and the RA are returned.
*/
static inline u8
mwifiex_find_stream_to_delete(struct mwifiex_private *priv,
struct mwifiex_ra_list_tbl *ptr, int ptr_tid,
int *ptid, u8 *ra)
{
int tid;
u8 ret = false;
struct mwifiex_tx_ba_stream_tbl *tx_tbl;
unsigned long flags;
tid = priv->aggr_prio_tbl[ptr_tid].ampdu_user;
spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags);
list_for_each_entry(tx_tbl, &priv->tx_ba_stream_tbl_ptr, list) {
if (tid > priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user) {
tid = priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user;
*ptid = tx_tbl->tid;
memcpy(ra, tx_tbl->ra, ETH_ALEN);
ret = true;
}
}
spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags);
return ret;
}
/*
* This function checks whether BA stream is set up or not.
*/
static inline int
mwifiex_is_ba_stream_setup(struct mwifiex_private *priv,
struct mwifiex_ra_list_tbl *ptr, int tid)
{
struct mwifiex_tx_ba_stream_tbl *tx_tbl;
tx_tbl = mwifiex_11n_get_tx_ba_stream_tbl(priv, tid, ptr->ra);
if (tx_tbl && IS_BASTREAM_SETUP(tx_tbl))
return true;
return false;
}
#endif /* !_MWIFIEX_11N_H_ */
/*
* Marvell Wireless LAN device driver: 802.11n Aggregation
*
* Copyright (C) 2011, Marvell International Ltd.
*
* This software file (the "File") is distributed by Marvell International
* Ltd. under the terms of the GNU General Public License Version 2, June 1991
* (the "License"). You may use, redistribute and/or modify this File in
* accordance with the terms and conditions of the License, a copy of which
* is available by writing to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
* worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
* this warranty disclaimer.
*/
#include "decl.h"
#include "ioctl.h"
#include "util.h"
#include "fw.h"
#include "main.h"
#include "wmm.h"
#include "11n.h"
#include "11n_aggr.h"
/*
* Creates an AMSDU subframe for aggregation into one AMSDU packet.
*
* The resultant AMSDU subframe format is -
*
* +---- ~ -----+---- ~ ------+---- ~ -----+----- ~ -----+---- ~ -----+
* | DA | SA | Length | SNAP header | MSDU |
* | data[0..5] | data[6..11] | | | data[14..] |
* +---- ~ -----+---- ~ ------+---- ~ -----+----- ~ -----+---- ~ -----+
* <--6-bytes--> <--6-bytes--> <--2-bytes--><--8-bytes--> <--n-bytes-->
*
* This function also computes the amount of padding required to make the
* buffer length multiple of 4 bytes.
*
* Data => |DA|SA|SNAP-TYPE|........ .|
* MSDU => |DA|SA|Length|SNAP|...... ..|
*/
static int
mwifiex_11n_form_amsdu_pkt(struct mwifiex_adapter *adapter,
struct sk_buff *skb_aggr,
struct sk_buff *skb_src, int *pad)
{
int dt_offset;
struct rfc_1042_hdr snap = {
0xaa, /* LLC DSAP */
0xaa, /* LLC SSAP */
0x03, /* LLC CTRL */
{0x00, 0x00, 0x00}, /* SNAP OUI */
0x0000 /* SNAP type */
/*
* This field will be overwritten
* later with ethertype
*/
};
struct tx_packet_hdr *tx_header = NULL;
skb_put(skb_aggr, sizeof(*tx_header));
tx_header = (struct tx_packet_hdr *) skb_aggr->data;
/* Copy DA and SA */
dt_offset = 2 * ETH_ALEN;
memcpy(&tx_header->eth803_hdr, skb_src->data, dt_offset);
/* Copy SNAP header */
snap.snap_type = *(u16 *) ((u8 *)skb_src->data + dt_offset);
dt_offset += sizeof(u16);
memcpy(&tx_header->rfc1042_hdr, &snap, sizeof(struct rfc_1042_hdr));
skb_pull(skb_src, dt_offset);
/* Update Length field */
tx_header->eth803_hdr.h_proto = htons(skb_src->len + LLC_SNAP_LEN);
/* Add payload */
skb_put(skb_aggr, skb_src->len);
memcpy(skb_aggr->data + sizeof(*tx_header), skb_src->data,
skb_src->len);
*pad = (((skb_src->len + LLC_SNAP_LEN) & 3)) ? (4 - (((skb_src->len +
LLC_SNAP_LEN)) & 3)) : 0;
skb_put(skb_aggr, *pad);
return skb_aggr->len + *pad;
}
/*
* Adds TxPD to AMSDU header.
*
* Each AMSDU packet will contain one TxPD at the beginning,
* followed by multiple AMSDU subframes.
*/
static void
mwifiex_11n_form_amsdu_txpd(struct mwifiex_private *priv,
struct sk_buff *skb)
{
struct txpd *local_tx_pd;
skb_push(skb, sizeof(*local_tx_pd));
local_tx_pd = (struct txpd *) skb->data;
memset(local_tx_pd, 0, sizeof(struct txpd));
/* Original priority has been overwritten */
local_tx_pd->priority = (u8) skb->priority;
local_tx_pd->pkt_delay_2ms =
mwifiex_wmm_compute_drv_pkt_delay(priv, skb);
local_tx_pd->bss_num = priv->bss_num;
local_tx_pd->bss_type = priv->bss_type;
/* Always zero as the data is followed by struct txpd */
local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd));
local_tx_pd->tx_pkt_type = cpu_to_le16(PKT_TYPE_AMSDU);
local_tx_pd->tx_pkt_length = cpu_to_le16(skb->len -
sizeof(*local_tx_pd));
if (local_tx_pd->tx_control == 0)
/* TxCtrl set by user or default */
local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl);
if ((GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA) &&
(priv->adapter->pps_uapsd_mode)) {
if (true == mwifiex_check_last_packet_indication(priv)) {
priv->adapter->tx_lock_flag = true;
local_tx_pd->flags =
MWIFIEX_TxPD_POWER_MGMT_LAST_PACKET;
}
}
}
/*
* Counts the number of subframes in an aggregate packet.
*
* This function parses an aggregate packet buffer, looking for
* subframes and counting the number of such subframe found. The
* function automatically skips the DA/SA fields at the beginning
* of each subframe and padding at the end.
*/
static int
mwifiex_11n_get_num_aggr_pkts(u8 *data, int total_pkt_len)
{
int pkt_count = 0, pkt_len, pad;
while (total_pkt_len > 0) {
/* Length will be in network format, change it to host */
pkt_len = ntohs((*(__be16 *)(data + 2 * ETH_ALEN)));
pad = (((pkt_len + sizeof(struct ethhdr)) & 3)) ?
(4 - ((pkt_len + sizeof(struct ethhdr)) & 3)) : 0;
data += pkt_len + pad + sizeof(struct ethhdr);
total_pkt_len -= pkt_len + pad + sizeof(struct ethhdr);
++pkt_count;
}
return pkt_count;
}
/*
* De-aggregate received packets.
*
* This function parses the received aggregate buffer, extracts each subframe,
* strips off the SNAP header from them and sends the data portion for further
* processing.
*
* Each subframe body is copied onto a separate buffer, which are freed by
* upper layer after processing. The function also performs sanity tests on
* the received buffer.
*/
int mwifiex_11n_deaggregate_pkt(struct mwifiex_private *priv,
struct sk_buff *skb)
{
u16 pkt_len;
int total_pkt_len;
u8 *data;
int pad;
struct mwifiex_rxinfo *rx_info = MWIFIEX_SKB_RXCB(skb);
struct rxpd *local_rx_pd = (struct rxpd *) skb->data;
struct sk_buff *skb_daggr;
struct mwifiex_rxinfo *rx_info_daggr = NULL;
int ret = -1;
struct rx_packet_hdr *rx_pkt_hdr;
struct mwifiex_adapter *adapter = priv->adapter;
u8 rfc1042_eth_hdr[ETH_ALEN] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00};
data = (u8 *) (local_rx_pd + local_rx_pd->rx_pkt_offset);
total_pkt_len = local_rx_pd->rx_pkt_length;
/* Sanity test */
if (total_pkt_len > MWIFIEX_RX_DATA_BUF_SIZE) {
dev_err(adapter->dev, "total pkt len greater than buffer"
" size %d\n", total_pkt_len);
return -1;
}
rx_info->use_count = mwifiex_11n_get_num_aggr_pkts(data, total_pkt_len);
while (total_pkt_len > 0) {
rx_pkt_hdr = (struct rx_packet_hdr *) data;
/* Length will be in network format, change it to host */
pkt_len = ntohs((*(__be16 *) (data + 2 * ETH_ALEN)));
if (pkt_len > total_pkt_len) {
dev_err(adapter->dev, "pkt_len %d > total_pkt_len %d\n",
total_pkt_len, pkt_len);
break;
}
pad = (((pkt_len + sizeof(struct ethhdr)) & 3)) ?
(4 - ((pkt_len + sizeof(struct ethhdr)) & 3)) : 0;
total_pkt_len -= pkt_len + pad + sizeof(struct ethhdr);
if (memcmp(&rx_pkt_hdr->rfc1042_hdr,
rfc1042_eth_hdr, sizeof(rfc1042_eth_hdr)) == 0) {
memmove(data + LLC_SNAP_LEN, data, 2 * ETH_ALEN);
data += LLC_SNAP_LEN;
pkt_len += sizeof(struct ethhdr) - LLC_SNAP_LEN;
} else {
*(u16 *) (data + 2 * ETH_ALEN) = (u16) 0;
pkt_len += sizeof(struct ethhdr);
}
skb_daggr = dev_alloc_skb(pkt_len);
if (!skb_daggr) {
dev_err(adapter->dev, "%s: failed to alloc skb_daggr\n",
__func__);
return -1;
}
rx_info_daggr = MWIFIEX_SKB_RXCB(skb_daggr);
rx_info_daggr->bss_index = rx_info->bss_index;
skb_daggr->tstamp = skb->tstamp;
rx_info_daggr->parent = skb;
skb_daggr->priority = skb->priority;
skb_put(skb_daggr, pkt_len);
memcpy(skb_daggr->data, data, pkt_len);
ret = mwifiex_recv_packet(adapter, skb_daggr);
switch (ret) {
case -EINPROGRESS:
break;
case -1:
dev_err(adapter->dev, "deaggr: host_to_card failed\n");
case 0:
mwifiex_recv_packet_complete(adapter, skb_daggr, ret);
break;
default:
break;
}
data += pkt_len + pad;
}
return ret;
}
/*
* Create aggregated packet.
*
* This function creates an aggregated MSDU packet, by combining buffers
* from the RA list. Each individual buffer is encapsulated as an AMSDU
* subframe and all such subframes are concatenated together to form the
* AMSDU packet.
*
* A TxPD is also added to the front of the resultant AMSDU packets for
* transmission. The resultant packets format is -
*
* +---- ~ ----+------ ~ ------+------ ~ ------+-..-+------ ~ ------+
* | TxPD |AMSDU sub-frame|AMSDU sub-frame| .. |AMSDU sub-frame|
* | | 1 | 2 | .. | n |
* +---- ~ ----+------ ~ ------+------ ~ ------+ .. +------ ~ ------+
*/
int
mwifiex_11n_aggregate_pkt(struct mwifiex_private *priv,
struct mwifiex_ra_list_tbl *pra_list, int headroom,
int ptrindex, unsigned long ra_list_flags)
__releases(&priv->wmm.ra_list_spinlock)
{
struct mwifiex_adapter *adapter = priv->adapter;
struct sk_buff *skb_aggr, *skb_src;
struct mwifiex_txinfo *tx_info_aggr, *tx_info_src;
int pad = 0;
int ret = 0;
struct mwifiex_tx_param tx_param;
struct txpd *ptx_pd = NULL;
if (skb_queue_empty(&pra_list->skb_head)) {
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
ra_list_flags);
return 0;
}
skb_src = skb_peek(&pra_list->skb_head);
tx_info_src = MWIFIEX_SKB_TXCB(skb_src);
skb_aggr = dev_alloc_skb(adapter->tx_buf_size);
if (!skb_aggr) {
dev_err(adapter->dev, "%s: alloc skb_aggr\n", __func__);
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
ra_list_flags);
return -1;
}
skb_reserve(skb_aggr, headroom + sizeof(struct txpd));
tx_info_aggr = MWIFIEX_SKB_TXCB(skb_aggr);
tx_info_aggr->bss_index = tx_info_src->bss_index;
skb_aggr->priority = skb_src->priority;
while (skb_src && ((skb_headroom(skb_aggr) + skb_src->len
+ LLC_SNAP_LEN)
<= adapter->tx_buf_size)) {
if (!skb_queue_empty(&pra_list->skb_head))
skb_src = skb_dequeue(&pra_list->skb_head);
else
skb_src = NULL;
pra_list->total_pkts_size -= skb_src->len;
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
ra_list_flags);
mwifiex_11n_form_amsdu_pkt(adapter, skb_aggr, skb_src, &pad);
mwifiex_write_data_complete(adapter, skb_src, 0);
spin_lock_irqsave(&priv->wmm.ra_list_spinlock, ra_list_flags);
if (!mwifiex_is_ralist_valid(priv, pra_list, ptrindex)) {
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
ra_list_flags);
return -1;
}
if (!skb_queue_empty(&pra_list->skb_head))
skb_src = skb_peek(&pra_list->skb_head);
else
skb_src = NULL;
}
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, ra_list_flags);
/* Last AMSDU packet does not need padding */
skb_trim(skb_aggr, skb_aggr->len - pad);
/* Form AMSDU */
mwifiex_11n_form_amsdu_txpd(priv, skb_aggr);
if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA)
ptx_pd = (struct txpd *)skb_aggr->data;
skb_push(skb_aggr, headroom);
tx_param.next_pkt_len = ((pra_list->total_pkts_size) ?
(((pra_list->total_pkts_size) >
adapter->tx_buf_size) ? adapter->
tx_buf_size : pra_list->total_pkts_size +
LLC_SNAP_LEN + sizeof(struct txpd)) : 0);
ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_TYPE_DATA,
skb_aggr->data,
skb_aggr->len, &tx_param);
switch (ret) {
case -EBUSY:
spin_lock_irqsave(&priv->wmm.ra_list_spinlock, ra_list_flags);
if (!mwifiex_is_ralist_valid(priv, pra_list, ptrindex)) {
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
ra_list_flags);
mwifiex_write_data_complete(adapter, skb_aggr, -1);
return -1;
}
if ((GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA) &&
(adapter->pps_uapsd_mode) &&
(adapter->tx_lock_flag)) {
priv->adapter->tx_lock_flag = false;
ptx_pd->flags = 0;
}
skb_queue_tail(&pra_list->skb_head, skb_aggr);
pra_list->total_pkts_size += skb_aggr->len;
tx_info_aggr->flags |= MWIFIEX_BUF_FLAG_REQUEUED_PKT;
spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
ra_list_flags);
dev_dbg(adapter->dev, "data: -EBUSY is returned\n");
break;
case -1:
adapter->data_sent = false;
dev_err(adapter->dev, "%s: host_to_card failed: %#x\n",
__func__, ret);
adapter->dbg.num_tx_host_to_card_failure++;
mwifiex_write_data_complete(adapter, skb_aggr, ret);
return 0;
case -EINPROGRESS:
adapter->data_sent = false;
break;
case 0:
mwifiex_write_data_complete(adapter, skb_aggr, ret);
break;