diff options
Diffstat (limited to 'drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_usb.c')
-rw-r--r-- | drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_usb.c | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_usb.c b/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_usb.c new file mode 100644 index 000000000000..5a5f2d8aa71e --- /dev/null +++ b/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_usb.c @@ -0,0 +1,1264 @@ +/** @file mlan_usb.c + * + * @brief This file contains USB specific code + * + * + * Copyright 2014-2020 NXP + * + * This software file (the File) is distributed by NXP + * under the terms of the GNU General Public License Version 2, June 1991 + * (the License). You may use, redistribute and/or modify the 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. + * + */ + +/******************************************************** +Change log: + 04/21/2009: initial version +********************************************************/ + +#include "mlan.h" +#ifdef STA_SUPPORT +#include "mlan_join.h" +#endif +#include "mlan_util.h" +#include "mlan_init.h" +#include "mlan_fw.h" +#include "mlan_main.h" + +/******************************************************** + Local Variables +********************************************************/ +#ifdef USB8897 +static const struct _mlan_card_info mlan_card_info_usb8897 = { + .max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K, + .v16_fw_api = 0, + .supp_ps_handshake = 1, + .default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2, +}; +#endif + +#ifdef USB8997 +static const struct _mlan_card_info mlan_card_info_usb8997 = { + .max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K, + .v16_fw_api = 1, + .supp_ps_handshake = 1, + .default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2, +}; +#endif + +#ifdef USB8978 +static const struct _mlan_card_info mlan_card_info_usb8978 = { + .max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K, + .v16_fw_api = 1, + .supp_ps_handshake = 1, + .default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2, +}; +#endif + +#ifdef USB9098 +static const struct _mlan_card_info mlan_card_info_usb9098 = { + .max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K, + .v16_fw_api = 1, + .v17_fw_api = 1, + .supp_ps_handshake = 1, + .default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2, +}; +#endif + +#ifdef USB9097 +static const struct _mlan_card_info mlan_card_info_usb9097 = { + .max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K, + .v16_fw_api = 1, + .v17_fw_api = 1, + .supp_ps_handshake = 1, + .default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2, +}; +#endif + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ +#if defined(USB9098) +/** + * @This function checks the chip revision id + * + * @param pmadapter A pointer to mlan_adapter structure + * @param rev_id A pointer to chip revision id + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +mlan_status wlan_usb_check_revision(mlan_adapter *pmadapter, t_u32 *rev_id) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_callbacks pcb = &pmadapter->callbacks; + mlan_buffer mbuf; + t_u8 *tx_buff = MNULL; + t_u8 *recv_buff = MNULL; + t_u8 tx_size = 16; + FWSyncPkt syncpkt; + + ENTER(); + /* Allocate memory for transmit */ + ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_TX_BUF_SIZE, + MLAN_MEM_DEF | MLAN_MEM_DMA, (t_u8 **)&tx_buff); + if ((ret != MLAN_STATUS_SUCCESS) || !tx_buff) { + PRINTM(MERROR, "Could not allocate buffer for FW download\n"); + LEAVE(); + return MLAN_STATUS_FAILURE; + } + /* Allocate memory for receive */ + ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_RX_BUF_SIZE, + MLAN_MEM_DEF | MLAN_MEM_DMA, &recv_buff); + if ((ret != MLAN_STATUS_SUCCESS) || !recv_buff) { + PRINTM(MERROR, + "Could not allocate buffer for FW download response\n"); + goto cleanup; + } + memset(pmadapter, &syncpkt, 0, sizeof(FWSyncPkt)); + memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer)); + mbuf.pbuf = (t_u8 *)tx_buff; + mbuf.data_len = tx_size; + ret = pcb->moal_write_data_sync(pmadapter->pmoal_handle, &mbuf, + pmadapter->tx_cmd_ep, + MLAN_USB_BULK_MSG_TIMEOUT); + if (ret != MLAN_STATUS_SUCCESS) { + PRINTM(MERROR, "check revision: write_data failed, ret %d\n", + ret); + goto cleanup; + } + memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer)); + mbuf.pbuf = (t_u8 *)recv_buff; + mbuf.data_len = 2048; + ret = pcb->moal_read_data_sync(pmadapter->pmoal_handle, &mbuf, + pmadapter->rx_cmd_ep, + MLAN_USB_BULK_MSG_TIMEOUT); + if (ret != MLAN_STATUS_SUCCESS) { + PRINTM(MERROR, "check revision: read_data failed, ret %d\n", + ret); + goto cleanup; + } + memcpy_ext(pmadapter, &syncpkt, recv_buff, sizeof(syncpkt), + sizeof(syncpkt)); + syncpkt.chip_rev = wlan_le32_to_cpu(syncpkt.chip_rev); + *rev_id = syncpkt.chip_rev & 0x000000ff; + PRINTM(MERROR, "chip_revision_id = %d\n", syncpkt.chip_rev); +cleanup: + if (recv_buff) + pcb->moal_mfree(pmadapter->pmoal_handle, recv_buff); + if (tx_buff) + pcb->moal_mfree(pmadapter->pmoal_handle, tx_buff); + + LEAVE(); + return ret; +} +#endif + +/** + * @brief This function downloads FW blocks to device + * + * @param pmadapter A pointer to mlan_adapter + * @param pmfw A pointer to firmware image + * + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +static mlan_status wlan_usb_prog_fw_w_helper(pmlan_adapter pmadapter, + pmlan_fw_image pmfw) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_callbacks pcb = &pmadapter->callbacks; + t_u8 *firmware = pmfw->pfw_buf, *RecvBuff; + t_u32 retries = MAX_FW_RETRY, DataLength; + t_u32 FWSeqNum = 0, TotalBytes = 0, DnldCmd = 0; + t_u8 *TxBuff = MNULL; + FWData *fwdata = MNULL; + FWSyncHeader SyncFWHeader; + t_u8 check_winner = 1; + t_u8 check_fw_status = MFALSE; + t_u8 mic_retry = MAX_FW_RETRY; +#if defined(USB9098) + t_u32 revision_id = 0; +#endif + + ENTER(); + + if (!firmware && !pcb->moal_get_fw_data) { + PRINTM(MMSG, "No firmware image found! Terminating download\n"); + ret = MLAN_STATUS_FAILURE; + goto fw_exit; + } + + /* Allocate memory for transmit */ + ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_TX_BUF_SIZE, + MLAN_MEM_DEF | MLAN_MEM_DMA, (t_u8 **)&TxBuff); + if ((ret != MLAN_STATUS_SUCCESS) || !TxBuff) { + PRINTM(MERROR, "Could not allocate buffer for FW download\n"); + goto fw_exit; + } + fwdata = (FWData *)TxBuff; + + /* Allocate memory for receive */ + ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_RX_BUF_SIZE, + MLAN_MEM_DEF | MLAN_MEM_DMA, &RecvBuff); + if ((ret != MLAN_STATUS_SUCCESS) || !RecvBuff) { + PRINTM(MERROR, + "Could not allocate buffer for FW download response\n"); + goto cleanup; + } + + if (!IS_USB_NEW_INIT(pmadapter->feature_control)) + check_winner = 0; +#if defined(USB9098) + if (IS_USB9098(pmadapter->card_type)) { + ret = wlan_usb_check_revision(pmadapter, &revision_id); + if (ret != MLAN_STATUS_SUCCESS) { + PRINTM(MERROR, "Failed to get USB chip revision ID\n"); + goto cleanup; + } + /* Skyhawk A0, need to check both CRC and MIC error */ + if (revision_id >= CHIP_9098_REV_A0) + check_fw_status = MTRUE; + } +#endif +#if defined(USB9097) + if (IS_USB9097(pmadapter->card_type)) + check_fw_status = MTRUE; +#endif + do { + /* Send pseudo data to check winner status first */ + if (check_winner) { + memset(pmadapter, &fwdata->fw_header, 0, + sizeof(FWHeader)); + DataLength = 0; + } else { + /* Copy the header of the firmware data to get the + * length */ + if (firmware) + memcpy_ext(pmadapter, &fwdata->fw_header, + &firmware[TotalBytes], + sizeof(FWHeader), + sizeof(fwdata->fw_header)); + else + pcb->moal_get_fw_data( + pmadapter->pmoal_handle, TotalBytes, + sizeof(FWHeader), + (t_u8 *)&fwdata->fw_header); + + DataLength = + wlan_le32_to_cpu(fwdata->fw_header.data_length); + DnldCmd = wlan_le32_to_cpu(fwdata->fw_header.dnld_cmd); + TotalBytes += sizeof(FWHeader); + + /** CMD 7 don't have data_length field */ + if (DnldCmd == FW_CMD_4 || DnldCmd == FW_CMD_6 || + DnldCmd == FW_CMD_7 || DnldCmd == FW_CMD_10) + DataLength = 0; + + if (DataLength > + (FW_DNLD_TX_BUF_SIZE - sizeof(FWHeader))) { + PRINTM(MERROR, + "Invalid Data Legth read from FW\n"); + ret = MLAN_STATUS_FAILURE; + break; + } + + /* Copy the firmware data */ + if (firmware) + memcpy_ext(pmadapter, fwdata->data, + &firmware[TotalBytes], DataLength, + DataLength); + else + pcb->moal_get_fw_data(pmadapter->pmoal_handle, + TotalBytes, DataLength, + (t_u8 *)fwdata->data); + + fwdata->seq_num = wlan_cpu_to_le32(FWSeqNum); + TotalBytes += DataLength; + } + /* If the send/receive fails or CRC occurs then retry */ + while (retries) { + mlan_buffer mbuf; + int length = FW_DATA_XMIT_SIZE; + retries--; + + memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer)); + mbuf.pbuf = (t_u8 *)fwdata; + mbuf.data_len = length; + /* Send the firmware block */ + ret = pcb->moal_write_data_sync( + pmadapter->pmoal_handle, &mbuf, + pmadapter->tx_cmd_ep, + MLAN_USB_BULK_MSG_TIMEOUT); + if (ret != MLAN_STATUS_SUCCESS) { + PRINTM(MERROR, + "fw_dnld: write_data failed, ret %d\n", + ret); + continue; + } + + memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer)); + mbuf.pbuf = RecvBuff; + mbuf.data_len = FW_DNLD_RX_BUF_SIZE; + + /* Receive the firmware block response */ + ret = pcb->moal_read_data_sync( + pmadapter->pmoal_handle, &mbuf, + pmadapter->rx_cmd_ep, + MLAN_USB_BULK_MSG_TIMEOUT); + if (ret != MLAN_STATUS_SUCCESS) { + PRINTM(MERROR, + "fw_dnld: read_data failed, ret %d\n", + ret); + continue; + } + memcpy_ext(pmadapter, &SyncFWHeader, RecvBuff, + sizeof(FWSyncHeader), sizeof(SyncFWHeader)); + endian_convert_syncfwheader(&SyncFWHeader); + /* Check the first firmware block response for highest + * bit set */ + if (check_winner) { + if (SyncFWHeader.cmd & 0x80000000) { + PRINTM(MMSG, + "USB is not the winner 0x%x, returning success\n", + SyncFWHeader.cmd); + ret = MLAN_STATUS_SUCCESS; + goto cleanup; + } + PRINTM(MINFO, + "USB is the winner, start to download FW\n"); + check_winner = 0; + break; + } + + /* Check the firmware block response for CRC errors */ + if (SyncFWHeader.cmd) { + /* Check firmware block response for CRC and MIC + * errors */ + if (check_fw_status) { + if (SyncFWHeader.status & MBIT(0)) { + PRINTM(MERROR, + "FW received Blk with CRC error 0x%x offset=%d\n", + SyncFWHeader.status, + SyncFWHeader.offset); + ret = MLAN_STATUS_FAILURE; + continue; + } + if (SyncFWHeader.status & + (MBIT(6) | MBIT(7))) { + PRINTM(MERROR, + "FW received Blk with MIC error 0x%x offset\n", + SyncFWHeader.status, + SyncFWHeader.offset); + mic_retry--; + FWSeqNum = 0; + TotalBytes = 0; + ret = MLAN_STATUS_FAILURE; + continue; + } + } else { + PRINTM(MERROR, + "FW received Blk with CRC error 0x%x\n", + SyncFWHeader.cmd); + ret = MLAN_STATUS_FAILURE; + continue; + } + } + + retries = MAX_FW_RETRY; + break; + } + + FWSeqNum++; + PRINTM(MINFO, ".\n"); + + } while ((DnldCmd != FW_HAS_LAST_BLOCK) && retries && mic_retry); + +cleanup: + PRINTM(MMSG, "fw_dnld: %d bytes downloaded\n", TotalBytes); + + if (RecvBuff) + pcb->moal_mfree(pmadapter->pmoal_handle, RecvBuff); + if (TxBuff) + pcb->moal_mfree(pmadapter->pmoal_handle, TxBuff); + if (retries && mic_retry) { + ret = MLAN_STATUS_SUCCESS; + } + +fw_exit: + LEAVE(); + return ret; +} + +/** + * @brief Get number of packets when deaggregated + * + * @param pmadapter A pointer to mlan_adapter + * @param pdata A pointer to packet data + * @param aggr_pkt_len Aggregate packet length + * + * @return Number of packets + */ +static int wlan_usb_deaggr_rx_num_pkts(pmlan_adapter pmadapter, t_u8 *pdata, + int aggr_pkt_len) +{ + int pkt_count = 0, pkt_len; + RxPD *prx_pd; + + ENTER(); + while (aggr_pkt_len >= sizeof(RxPD)) { + prx_pd = (RxPD *)pdata; + pkt_len = wlan_le16_to_cpu(prx_pd->rx_pkt_length) + + wlan_le16_to_cpu(prx_pd->rx_pkt_offset); + if (pkt_len == 0) /* blank RxPD can be at the end */ + break; + + ++pkt_count; + if (aggr_pkt_len == pkt_len) /* last packet has no padding */ + break; + + /* skip padding and goto next */ + if (pkt_len % + pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.aggr_align) + pkt_len += + (pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl + .aggr_align - + (pkt_len % pmadapter->pcard_usb->usb_rx_deaggr + .aggr_ctrl.aggr_align)); + aggr_pkt_len -= pkt_len; + pdata += pkt_len; + } + LEAVE(); + return pkt_count; +} + +static inline t_u32 usb_tx_aggr_pad_len(t_u32 len, + usb_tx_aggr_params *pusb_tx_aggr) +{ + return (len % pusb_tx_aggr->aggr_ctrl.aggr_align) ? + (len + (pusb_tx_aggr->aggr_ctrl.aggr_align - + (len % pusb_tx_aggr->aggr_ctrl.aggr_align))) : + len; +} + +/** + * @brief Copy pmbuf to aggregation buffer + * + * @param pmadapter Pointer to mlan_adapter structure + * @param pmbuf_aggr Pointer to aggregation buffer + * @param pmbuf Pointer to buffer to copy + * @param pusb_tx_aggr Pointer to usb_tx_aggr_params + * + * @return N/A + */ +static inline t_void +wlan_usb_tx_copy_buf_to_aggr(pmlan_adapter pmadapter, pmlan_buffer pmbuf_aggr, + pmlan_buffer pmbuf, + usb_tx_aggr_params *pusb_tx_aggr) +{ + ENTER(); + pmbuf_aggr->data_len = + usb_tx_aggr_pad_len(pmbuf_aggr->data_len, pusb_tx_aggr); + memcpy_ext(pmadapter, + pmbuf_aggr->pbuf + pmbuf_aggr->data_offset + + pmbuf_aggr->data_len, + pmbuf->pbuf + pmbuf->data_offset, pmbuf->data_len, + pmbuf->data_len); + pmbuf_aggr->data_len += pmbuf->data_len; + LEAVE(); +} + +#define MLAN_TYPE_AGGR_DATA_V2 11 +/** + * @brief Copy pmbuf to aggregation buffer + * + * @param pmadapter Pointer to mlan_adapter structure + * @param pmbuf_aggr Pointer to aggregation buffer + * @param pmbuf Pointer to buffer to copy + * @param last last packet flag + * @param pusb_tx_aggr Pointer to usb_tx_aggr_params + * + * @return N/A + */ +static inline t_void +wlan_usb_tx_copy_buf_to_aggr_v2(pmlan_adapter pmadapter, + pmlan_buffer pmbuf_aggr, pmlan_buffer pmbuf, + t_u8 last, usb_tx_aggr_params *pusb_tx_aggr) +{ + t_u8 *payload; + t_u16 offset; + + ENTER(); + pmbuf_aggr->data_len = + usb_tx_aggr_pad_len(pmbuf_aggr->data_len, pusb_tx_aggr); + memcpy_ext(pmadapter, + pmbuf_aggr->pbuf + pmbuf_aggr->data_offset + + pmbuf_aggr->data_len, + pmbuf->pbuf + pmbuf->data_offset, pmbuf->data_len, + pmbuf->data_len); + payload = pmbuf_aggr->pbuf + pmbuf_aggr->data_offset + + pmbuf_aggr->data_len; + if (last) { + offset = pmbuf->data_len; + *(t_u16 *)&payload[2] = + wlan_cpu_to_le16(MLAN_TYPE_AGGR_DATA_V2 | 0x80); + } else { + offset = usb_tx_aggr_pad_len(pmbuf->data_len, pusb_tx_aggr); + *(t_u16 *)&payload[2] = + wlan_cpu_to_le16(MLAN_TYPE_AGGR_DATA_V2); + } + *(t_u16 *)&payload[0] = wlan_cpu_to_le16(offset); + pmbuf_aggr->data_len += pmbuf->data_len; + PRINTM(MIF_D, "offset=%d len=%d\n", offset, pmbuf->data_len); + LEAVE(); +} + +/** + * @brief Allocate Aggregation buffer and copy pending buffers to it. + * + * @param pmadapter Pointer to mlan_adapter structure + * @param pusb_tx_aggr Pointer to usb_tx_aggr_params + * + * @return Aggregation buffer + */ +static inline pmlan_buffer +wlan_usb_copy_buf_to_aggr(pmlan_adapter pmadapter, + usb_tx_aggr_params *pusb_tx_aggr) +{ + pmlan_buffer pmbuf_aggr = MNULL; + t_u8 i, use_count; + pmlan_buffer pmbuf_curr, pmbuf_next; + pmbuf_aggr = wlan_alloc_mlan_buffer(pmadapter, pusb_tx_aggr->aggr_len, + 0, MOAL_MALLOC_BUFFER); + if (pmbuf_aggr) { + pmbuf_curr = pusb_tx_aggr->pmbuf_aggr; + pmbuf_aggr->bss_index = pmbuf_curr->bss_index; + pmbuf_aggr->buf_type = pmbuf_curr->buf_type; + pmbuf_aggr->priority = pmbuf_curr->priority; + pmbuf_aggr->data_len = 0; + PRINTM(MIF_D, "use_count=%d,aggr_len=%d\n", + pmbuf_curr->use_count, pusb_tx_aggr->aggr_len); + use_count = pmbuf_curr->use_count; + for (i = 0; i <= use_count; i++) { + pmbuf_next = pmbuf_curr->pnext; + if (pusb_tx_aggr->aggr_ctrl.aggr_mode == + MLAN_USB_AGGR_MODE_LEN_V2) { + if (i == use_count) + wlan_usb_tx_copy_buf_to_aggr_v2( + pmadapter, pmbuf_aggr, + pmbuf_curr, MTRUE, + pusb_tx_aggr); + else + wlan_usb_tx_copy_buf_to_aggr_v2( + pmadapter, pmbuf_aggr, + pmbuf_curr, MFALSE, + pusb_tx_aggr); + } else + wlan_usb_tx_copy_buf_to_aggr(pmadapter, + pmbuf_aggr, + pmbuf_curr, + pusb_tx_aggr); + pmbuf_curr = pmbuf_next; + } + DBG_HEXDUMP(MIF_D, "USB AggrTx", + pmbuf_aggr->pbuf + pmbuf_aggr->data_offset, + pmbuf_aggr->data_len); + } + return pmbuf_aggr; +} + +/** + * @brief Link buffer into aggregate head buffer + * + * @param pmbuf_aggr Pointer to aggregation buffer + * @param pmbuf Pointer to buffer to add to the buffer list + * @param pusb_tx_aggr Pointer to usb_tx_aggr_params + */ +static inline t_void +wlan_usb_tx_link_buf_to_aggr(pmlan_buffer pmbuf_aggr, pmlan_buffer pmbuf, + usb_tx_aggr_params *pusb_tx_aggr) +{ + /* link new buf at end of list */ + pmbuf->pnext = pmbuf_aggr; + pmbuf->pprev = pmbuf_aggr->pprev; + pmbuf->pparent = pmbuf_aggr; + pmbuf_aggr->pprev->pnext = pmbuf; + pmbuf_aggr->pprev = pmbuf; + pmbuf_aggr->use_count++; + pusb_tx_aggr->aggr_len = + usb_tx_aggr_pad_len(pusb_tx_aggr->aggr_len, pusb_tx_aggr); + pusb_tx_aggr->aggr_len += pmbuf->data_len; +} + +/** + * @brief Send aggregated buffer + * + * @param pmadapter Pointer to mlan_adapter structure + * @param pusb_tx_aggr Pointer to usb_tx_aggr_params + */ +static inline t_void wlan_usb_tx_send_aggr(pmlan_adapter pmadapter, + usb_tx_aggr_params *pusb_tx_aggr) +{ + mlan_status ret; + pmlan_buffer pmbuf_aggr = pusb_tx_aggr->pmbuf_aggr; + ENTER(); + if (!pusb_tx_aggr->pmbuf_aggr) { + LEAVE(); + return; + } + + if (pusb_tx_aggr->pmbuf_aggr->use_count) { + pmbuf_aggr = wlan_usb_copy_buf_to_aggr(pmadapter, pusb_tx_aggr); + /* allocate new buffer for aggregation if not exist */ + if (!pmbuf_aggr) { + PRINTM(MERROR, + "Error allocating [usb_tx] aggr mlan_buffer.\n"); + pmadapter->dbg.num_tx_host_to_card_failure += + pusb_tx_aggr->pmbuf_aggr->use_count; + wlan_write_data_complete(pmadapter, + pusb_tx_aggr->pmbuf_aggr, + MLAN_STATUS_FAILURE); + pusb_tx_aggr->pmbuf_aggr = MNULL; + pusb_tx_aggr->aggr_len = 0; + LEAVE(); + return; + } else { + wlan_write_data_complete(pmadapter, + pusb_tx_aggr->pmbuf_aggr, + MLAN_STATUS_SUCCESS); + pusb_tx_aggr->pmbuf_aggr = MNULL; + pusb_tx_aggr->aggr_len = 0; + } + } else if (pusb_tx_aggr->aggr_ctrl.aggr_mode == + MLAN_USB_AGGR_MODE_LEN_V2) { + t_u8 *payload = pmbuf_aggr->pbuf + pmbuf_aggr->data_offset; + *(t_u16 *)&payload[0] = wlan_cpu_to_le16(pmbuf_aggr->data_len); + *(t_u16 *)&payload[2] = + wlan_cpu_to_le16(MLAN_TYPE_AGGR_DATA_V2 | 0x80); + PRINTM(MIF_D, "USB Send single packet len=%d\n", + pmbuf_aggr->data_len); + DBG_HEXDUMP(MIF_D, "USB Tx", + pmbuf_aggr->pbuf + pmbuf_aggr->data_offset, + pmbuf_aggr->data_len); + } + + if (pmbuf_aggr && pmbuf_aggr->data_len) { + pmadapter->data_sent = MTRUE; + ret = pmadapter->callbacks.moal_write_data_async( + pmadapter->pmoal_handle, pmbuf_aggr, + pusb_tx_aggr->port); + switch (ret) { + case MLAN_STATUS_PRESOURCE: + PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n"); + break; + case MLAN_STATUS_RESOURCE: + /* Shouldn't reach here due to next condition. */ + /* TODO: (maybe) How to requeue the aggregate? */ + /* It may occur when the pending tx urbs reach the high + * mark */ + /* Thus, block further pkts for a bit */ + PRINTM(MERROR, + "Error: moal_write_data_async failed: 0x%X\n", + ret); + pmadapter->dbg.num_tx_host_to_card_failure++; + pmbuf_aggr->status_code = MLAN_ERROR_DATA_TX_FAIL; + wlan_write_data_complete(pmadapter, pmbuf_aggr, ret); + break; + case MLAN_STATUS_FAILURE: + pmadapter->data_sent = MFALSE; + PRINTM(MERROR, + "Error: moal_write_data_async failed: 0x%X\n", + ret); + pmadapter->dbg.num_tx_host_to_card_failure++; + pmbuf_aggr->status_code = MLAN_ERROR_DATA_TX_FAIL; + wlan_write_data_complete(pmadapter, pmbuf_aggr, ret); + break; + case MLAN_STATUS_PENDING: + pmadapter->data_sent = MFALSE; + break; + case MLAN_STATUS_SUCCESS: + wlan_write_data_complete(pmadapter, pmbuf_aggr, ret); + break; + default: + break; + } + + /* aggr_buf now sent to bus, prevent others from using it */ + pusb_tx_aggr->pmbuf_aggr = MNULL; + pusb_tx_aggr->aggr_len = 0; + } + LEAVE(); +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function get pcie device from card type + * + * @param pmadapter A pointer to mlan_adapter structure + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +mlan_status wlan_get_usb_device(pmlan_adapter pmadapter) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + t_u16 card_type = pmadapter->card_type; + + ENTER(); + + ret = pmadapter->callbacks.moal_malloc(pmadapter->pmoal_handle, + sizeof(mlan_usb_card), + MLAN_MEM_DEF, + (t_u8 **)&pmadapter->pcard_usb); + if (ret != MLAN_STATUS_SUCCESS || !pmadapter->pcard_usb) { + PRINTM(MERROR, "Failed to allocate pcard_usb\n"); + LEAVE(); + return MLAN_STATUS_FAILURE; + } + + switch (card_type) { +#ifdef USB8897 + case CARD_TYPE_USB8897: + pmadapter->pcard_info = &mlan_card_info_usb8897; + break; +#endif +#ifdef USB8997 + case CARD_TYPE_USB8997: + pmadapter->pcard_info = &mlan_card_info_usb8997; + break; +#endif +#ifdef USB8978 + case CARD_TYPE_USB8978: + pmadapter->pcard_info = &mlan_card_info_usb8978; + break; +#endif +#ifdef USB9098 + case CARD_TYPE_USB9098: + pmadapter->pcard_info = &mlan_card_info_usb9098; + break; +#endif +#ifdef USB9097 + case CARD_TYPE_USB9097: + pmadapter->pcard_info = &mlan_card_info_usb9097; + break; +#endif + default: + PRINTM(MERROR, "can't get right USB card type \n"); + ret = MLAN_STATUS_FAILURE; + break; + } + + LEAVE(); + return ret; +} + +/** + * @brief This function downloads firmware to card + * + * @param pmadapter A pointer to mlan_adapter + * @param pmfw A pointer to firmware image + * + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +mlan_status wlan_usb_dnld_fw(pmlan_adapter pmadapter, pmlan_fw_image pmfw) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + + ENTER(); + + ret = wlan_usb_prog_fw_w_helper(pmadapter, pmfw); + if (ret != MLAN_STATUS_SUCCESS) { + LEAVE(); + return MLAN_STATUS_FAILURE; + } + + LEAVE(); + return ret; +} + +/** + * @brief This function deaggregates USB RX Data Packet from device + * + * @param pmadapter A pointer to mlan_adapter + * @param pmbuf A pointer to the received buffer + * + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +mlan_status wlan_usb_deaggr_rx_pkt(pmlan_adapter pmadapter, pmlan_buffer pmbuf) +{ + const t_u8 zero_rx_pd[sizeof(RxPD)] = {0}; + mlan_status ret = MLAN_STATUS_SUCCESS; + t_u32 curr_pkt_len; + RxPD *prx_pd; + t_u8 *pdata; + t_s32 aggr_len; + pmlan_buffer pdeaggr_buf; + + ENTER(); + + pdata = pmbuf->pbuf + pmbuf->data_offset; + prx_pd = (RxPD *)pdata; + curr_pkt_len = wlan_le16_to_cpu(prx_pd->rx_pkt_length) + + wlan_le16_to_cpu(prx_pd->rx_pkt_offset); + /* if non-aggregate, just send through, don’t process here */ + aggr_len = pmbuf->data_len; + if ((aggr_len == curr_pkt_len) || + (wlan_usb_deaggr_rx_num_pkts(pmadapter, pdata, aggr_len) == 1) || + (pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.enable != MTRUE)) { + ret = wlan_handle_rx_packet(pmadapter, pmbuf); + LEAVE(); + return ret; + } + + while (aggr_len >= sizeof(RxPD)) { + /* check for (all-zeroes) termination RxPD */ + if (!memcmp(pmadapter, pdata, zero_rx_pd, sizeof(RxPD))) { + break; + } + + /* make new buffer and copy packet to it (including RxPD). + * Also, reserve headroom so that there must have space + * to change RxPD to TxPD for bridge packet in uAP mode */ + pdeaggr_buf = wlan_alloc_mlan_buffer(pmadapter, curr_pkt_len, + MLAN_RX_HEADER_LEN, + MOAL_ALLOC_MLAN_BUFFER); + if (pdeaggr_buf == MNULL) { + PRINTM(MERROR, + "Error allocating [usb_rx] deaggr mlan_buffer\n"); + ret = MLAN_STATUS_FAILURE; + break; + } + pdeaggr_buf->bss_index = pmbuf->bss_index; + pdeaggr_buf->buf_type = pmbuf->buf_type; + pdeaggr_buf->data_len = curr_pkt_len; + pdeaggr_buf->in_ts_sec = pmbuf->in_ts_sec; + pdeaggr_buf->in_ts_usec = pmbuf->in_ts_usec; + pdeaggr_buf->priority = pmbuf->priority; + memcpy_ext(pmadapter, + pdeaggr_buf->pbuf + pdeaggr_buf->data_offset, pdata, + curr_pkt_len, pdeaggr_buf->data_len); + + /* send new packet to processing */ + ret = wlan_handle_rx_packet(pmadapter, pdeaggr_buf); + if (ret == MLAN_STATUS_FAILURE) { + break; + } + /* last block has no padding bytes */ + if (aggr_len == curr_pkt_len) { + break; + } + + /* round up to next block boundary */ + if (curr_pkt_len % + pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.aggr_align) + curr_pkt_len += (pmadapter->pcard_usb->usb_rx_deaggr + .aggr_ctrl.aggr_align - + (curr_pkt_len % + pmadapter->pcard_usb->usb_rx_deaggr + .aggr_ctrl.aggr_align)); + /* point to next packet */ + aggr_len -= curr_pkt_len; + pdata += curr_pkt_len; + prx_pd = (RxPD *)pdata; + curr_pkt_len = wlan_le16_to_cpu(prx_pd->rx_pkt_length) + + wlan_le16_to_cpu(prx_pd->rx_pkt_offset); + } + + /* free original pmbuf (since not sent for processing) */ + pmadapter->callbacks.moal_recv_complete(pmadapter->pmoal_handle, pmbuf, + pmadapter->rx_data_ep, ret); + LEAVE(); + return ret; +} + +/** + * @brief This function restore tx_pause flag + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pusb_tx_aggr A pointer to usb_tx_aggr_params + * + * @return MTRUE/MFALSE + */ +t_u8 wlan_is_port_tx_paused(pmlan_adapter pmadapter, + usb_tx_aggr_params *pusb_tx_aggr) +{ + mlan_private *pmpriv = MNULL; + t_u8 i; + t_u8 ret = MFALSE; + for (i = 0; i < pmadapter->priv_num; i++) { + pmpriv = pmadapter->priv[i]; + if (pmpriv && pmpriv->tx_pause && + (pmpriv->port == pusb_tx_aggr->port)) { + ret = MTRUE; + break; + } + } + return ret; +} + +/** + * @brief This function handles the timeout of usb tx aggregation. + * It will send the aggregate buffer being held. + * + * @param function_context A pointer to function_context + * @return N/A + */ +t_void wlan_usb_tx_aggr_timeout_func(t_void *function_context) +{ + usb_tx_aggr_params *pusb_tx_aggr = + (usb_tx_aggr_params *)function_context; + pmlan_adapter pmadapter = (mlan_adapter *)pusb_tx_aggr->phandle; + pmlan_callbacks pcb = &pmadapter->callbacks; + + ENTER(); + pcb->moal_spin_lock(pmadapter->pmoal_handle, pusb_tx_aggr->paggr_lock); + pusb_tx_aggr->aggr_hold_timer_is_set = MFALSE; + if (pusb_tx_aggr->pmbuf_aggr && !pmadapter->data_sent && + !wlan_is_port_tx_paused(pmadapter, pusb_tx_aggr)) + wlan_usb_tx_send_aggr(pmadapter, pusb_tx_aggr); + pcb->moal_spin_unlock(pmadapter->pmoal_handle, + pusb_tx_aggr->paggr_lock); + LEAVE(); +} + +/** + * @brief This function aggregates USB TX Data Packet to send to device + * + * @param pmadapter A pointer to mlan_adapter + * @param pmbuf A pointer to the transmit buffer + * @param tx_param A pointer to mlan_tx_param + * @param pusb_tx_aggr A pointer to usb_tx_aggr_params + * + * @return MLAN_STATUS_PENDING or MLAN_STATUS_FAILURE + */ +/* + * Non Scatter-Gather code creates a new large buffer where each incoming + * buffer's data contents are copied to (aligned to USB boundaries). + * The individual buffers are ALSO linked to the large buffer, + * in order to handle complete AFTER the aggregate is sent. + * pmbuf_aggr->data_len is used to keep track of bytes aggregated so far. + */ +mlan_status wlan_usb_host_to_card_aggr(pmlan_adapter pmadapter, + pmlan_buffer pmbuf, + mlan_tx_param *tx_param, + usb_tx_aggr_params *pusb_tx_aggr) +{ + pmlan_callbacks pcb = &pmadapter->callbacks; + pmlan_buffer pmbuf_aggr; + mlan_status ret = MLAN_STATUS_PENDING; + t_u32 next_pkt_len = (tx_param) ? tx_param->next_pkt_len : 0; + t_u32 aggr_len_counter = 0; + /* indicators */ + t_u8 f_precopy_cur_buf = 0; + t_u8 f_send_aggr_buf = 0; + t_u8 f_postcopy_cur_buf = 0; + t_u32 max_aggr_size = 0, max_aggr_num = 0; + + ENTER(); + + pcb->moal_spin_lock(pmadapter->pmoal_handle, pusb_tx_aggr->paggr_lock); + + /* stop timer while we process */ + if (pusb_tx_aggr->aggr_hold_timer_is_set) { + pcb->moal_stop_timer(pmadapter->pmoal_handle, + pusb_tx_aggr->paggr_hold_timer); + pusb_tx_aggr->aggr_hold_timer_is_set = MFALSE; + } + + pmbuf_aggr = pusb_tx_aggr->pmbuf_aggr; + + if (pusb_tx_aggr->aggr_ctrl.aggr_tmo == MLAN_USB_TX_AGGR_TIMEOUT_DYN) { + if (!pmbuf_aggr) { + /* Start aggr from min timeout value in micro sec */ + pusb_tx_aggr->hold_timeout_msec = + MLAN_USB_TX_MIN_AGGR_TIMEOUT; + } else { + /* Increase timeout in milisecond if pkts are + * consecutive */ + if (pusb_tx_aggr->hold_timeout_msec < + MLAN_USB_TX_MAX_AGGR_TIMEOUT) + pusb_tx_aggr->hold_timeout_msec++; + } + } else { + if (pusb_tx_aggr->aggr_ctrl.aggr_tmo) + pusb_tx_aggr->hold_timeout_msec = + pusb_tx_aggr->aggr_ctrl.aggr_tmo / 1000; + } + + max_aggr_size = max_aggr_num = pusb_tx_aggr->aggr_ctrl.aggr_max; + if (pusb_tx_aggr->aggr_ctrl.aggr_mode == MLAN_USB_AGGR_MODE_NUM) { + max_aggr_size *= MAX(MLAN_USB_MAX_PKT_SIZE, + pusb_tx_aggr->aggr_ctrl.aggr_align); + } + if (pusb_tx_aggr->aggr_ctrl.aggr_mode == MLAN_USB_AGGR_MODE_LEN) + max_aggr_num /= pusb_tx_aggr->aggr_ctrl.aggr_align; + else if (pusb_tx_aggr->aggr_ctrl.aggr_mode == MLAN_USB_AGGR_MODE_LEN_V2) + max_aggr_num = MLAN_USB_TX_AGGR_MAX_NUM; + if (!pmbuf_aggr) { + /* use this buf to start linked list, that's it */ + pmbuf->pnext = pmbuf->pprev = pmbuf; + pmbuf_aggr = pmbuf; + pusb_tx_aggr->pmbuf_aggr = pmbuf_aggr; + pusb_tx_aggr->aggr_len = pmbuf->data_len; + pmbuf->flags |= MLAN_BUF_FLAG_USB_TX_AGGR; + + } else { + /* DECIDE what to do */ + aggr_len_counter = usb_tx_aggr_pad_len(pusb_tx_aggr->aggr_len, + pusb_tx_aggr); + + if ((aggr_len_counter + pmbuf->data_len) < max_aggr_size) { + f_precopy_cur_buf = 1; /* can fit current packet in aggr + */ + if (next_pkt_len) { + aggr_len_counter += usb_tx_aggr_pad_len( + pmbuf->data_len, pusb_tx_aggr); + if ((aggr_len_counter + next_pkt_len) >= + max_aggr_size) + f_send_aggr_buf = 1; /* can't fit next + packet, send now + */ + } + } else { + /* can't fit current packet */ + if (pusb_tx_aggr->aggr_len) + f_send_aggr_buf = 1; /* send aggr first */ + f_postcopy_cur_buf = 1; /* then copy into new aggr_buf + */ + } + } + + /* For zero timeout and zero next packet length send pkt now */ + if (!pusb_tx_aggr->aggr_ctrl.aggr_tmo && !next_pkt_len) + f_send_aggr_buf = 1; + + /* PERFORM ACTIONS as decided */ + if (f_precopy_cur_buf) { + PRINTM(MIF_D, "%s: Precopy current buffer.\n", __FUNCTION__); + wlan_usb_tx_link_buf_to_aggr(pmbuf_aggr, pmbuf, pusb_tx_aggr); + } + if (pmbuf_aggr->use_count + 1 >= max_aggr_num) + f_send_aggr_buf = 1; + + if (pmbuf->flags & MLAN_BUF_FLAG_NULL_PKT || + pmbuf->flags & MLAN_BUF_FLAG_TCP_ACK) + f_send_aggr_buf = 1; + + if (f_send_aggr_buf) { + PRINTM(MIF_D, "%s: Send aggregate buffer.\n", __FUNCTION__); + wlan_usb_tx_send_aggr(pmadapter, pusb_tx_aggr); + pmbuf_aggr = pusb_tx_aggr->pmbuf_aggr; /* update ptr */ + } + + if (f_postcopy_cur_buf) { + PRINTM(MIF_D, "%s: Postcopy current buffer.\n", __FUNCTION__); + if (!pmbuf_aggr) { /* this is possible if just sent (above) */ + /* use this buf to start linked list */ + pmbuf->pnext = pmbuf->pprev = pmbuf; + pmbuf_aggr = pmbuf; + pusb_tx_aggr->pmbuf_aggr = pmbuf_aggr; + pusb_tx_aggr->aggr_len = pmbuf->data_len; + pmbuf->flags |= MLAN_BUF_FLAG_USB_TX_AGGR; + } + } + /* (re)start timer if there is something in the aggregation buffer */ + if (pmbuf_aggr && pmbuf_aggr->data_len) { + if (pusb_tx_aggr->aggr_ctrl.aggr_tmo) { + pcb->moal_start_timer(pmadapter->pmoal_handle, + pusb_tx_aggr->paggr_hold_timer, + MFALSE, + pusb_tx_aggr->hold_timeout_msec); + pusb_tx_aggr->aggr_hold_timer_is_set = MTRUE; + } + } + + pcb->moal_spin_unlock(pmadapter->pmoal_handle, + pusb_tx_aggr->paggr_lock); + LEAVE(); + return ret; +} + +/** + * @brief This function wakes up the card. + * + * @param pmadapter A pointer to mlan_adapter structure + * @param timeout set timeout flag + * + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +mlan_status wlan_pm_usb_wakeup_card(pmlan_adapter pmadapter, t_u8 timeout) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + t_u32 age_ts_usec; + + ENTER(); + PRINTM(MEVENT, "Wakeup device...\n"); + pmadapter->callbacks.moal_get_system_time(pmadapter->pmoal_handle, + &pmadapter->pm_wakeup_in_secs, + &age_ts_usec); + + /* Simulation of HS_AWAKE event */ + pmadapter->pm_wakeup_fw_try = MFALSE; + pmadapter->pm_wakeup_card_req = MFALSE; + /* TODO USB suspend/resume */ + pmadapter->ps_state = PS_STATE_AWAKE; + LEAVE(); + return ret; +} + +/** + * @brief This function downloads data from driver to card. + * + * Both commands and data packets are transferred to the card + * by this function. This function adds the PCIE specific header + * to the front of the buffer before transferring. The header + * contains the length of the packet and the type. The firmware + * handles the packets based upon this set type. + * + * @param pmpriv A pointer to pmlan_private structure + * @param type data or command + * @param pmbuf A pointer to mlan_buffer (pmbuf->data_len should include + * PCIE header) + * @param tx_param A pointer to mlan_tx_param (can be MNULL if type is + * command) + * + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +mlan_status wlan_usb_host_to_card(pmlan_private pmpriv, t_u8 type, + mlan_buffer *pmbuf, mlan_tx_param *tx_param) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + usb_tx_aggr_params *pusb_tx_aggr = MNULL; + pmlan_adapter pmadapter = pmpriv->adapter; + + ENTER(); + + if (!pmbuf) { + PRINTM(MERROR, "Passed NULL pmbuf to %s\n", __FUNCTION__); + return MLAN_STATUS_FAILURE; + } + if (type == MLAN_TYPE_CMD +#if (defined(USB9098) || defined(USB9097)) + || type == MLAN_TYPE_VDLL +#endif + ) { + pmadapter->cmd_sent = MTRUE; + ret = pmadapter->callbacks.moal_write_data_async( + pmadapter->pmoal_handle, pmbuf, pmadapter->tx_cmd_ep); + if (ret == MLAN_STATUS_FAILURE) + pmadapter->cmd_sent = MFALSE; + LEAVE(); + return ret; + } + pusb_tx_aggr = wlan_get_usb_tx_aggr_params(pmadapter, pmpriv->port); + if (pusb_tx_aggr) { + ret = wlan_usb_host_to_card_aggr(pmadapter, pmbuf, tx_param, + pusb_tx_aggr); + } else { + pmadapter->data_sent = MTRUE; + ret = pmadapter->callbacks.moal_write_data_async( + pmadapter->pmoal_handle, pmbuf, pmpriv->port); + switch (ret) { + case MLAN_STATUS_PRESOURCE: + PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n"); + break; + case MLAN_STATUS_RESOURCE: + + break; + case MLAN_STATUS_FAILURE: + pmadapter->data_sent = MFALSE; + break; + case MLAN_STATUS_PENDING: + pmadapter->data_sent = MFALSE; + break; + case MLAN_STATUS_SUCCESS: + break; + default: + break; + } + } + + LEAVE(); + return ret; +} + +/** + * @brief This function handle event/cmd complete + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pmbuf A pointer to the mlan_buffer + * @return N/A + */ +mlan_status wlan_usb_cmdevt_complete(pmlan_adapter pmadapter, + mlan_buffer *pmbuf, mlan_status status) +{ + ENTER(); + + pmadapter->callbacks.moal_recv_complete(pmadapter->pmoal_handle, pmbuf, + pmadapter->rx_cmd_ep, status); + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handle data complete + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pmbuf A pointer to the mlan_buffer + * @return N/A + */ +mlan_status wlan_usb_data_complete(pmlan_adapter pmadapter, mlan_buffer *pmbuf, + mlan_status status) +{ + ENTER(); + + pmadapter->callbacks.moal_recv_complete(pmadapter->pmoal_handle, pmbuf, + pmadapter->rx_data_ep, status); + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handle receive packet + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pmbuf A pointer to the mlan_buffer + * @return + */ +mlan_status wlan_usb_handle_rx_packet(mlan_adapter *pmadapter, + pmlan_buffer pmbuf) +{ + ENTER(); + + if (pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.enable == MTRUE) + return wlan_usb_deaggr_rx_pkt(pmadapter, pmbuf); + else + return wlan_handle_rx_packet(pmadapter, pmbuf); + + LEAVE(); +} + +mlan_adapter_operations mlan_usb_ops = { + .dnld_fw = wlan_usb_dnld_fw, + .host_to_card = wlan_usb_host_to_card, + .wakeup_card = wlan_pm_usb_wakeup_card, + .event_complete = wlan_usb_cmdevt_complete, + .data_complete = wlan_usb_data_complete, + .cmdrsp_complete = wlan_usb_cmdevt_complete, + .handle_rx_packet = wlan_usb_handle_rx_packet, + + .intf_header_len = USB_INTF_HEADER_LEN, +}; |