blob: 14746dd8db7830e01afa89aa0d23f36ef6c46b7d [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
******************************************************************************/
#define _RTL8192C_XMIT_C_
#include <osdep_service.h>
#include <drv_types.h>
#include <wifi.h>
#include <osdep_intf.h>
#include <usb_ops.h>
/* include <rtl8192c_hal.h> */
#include <rtl8723a_hal.h>
static int urb_zero_packet_chk(struct rtw_adapter *padapter, int sz)
{
int blnSetTxDescOffset;
struct dvobj_priv *pdvobj = adapter_to_dvobj(padapter);
if (pdvobj->ishighspeed) {
if (((sz + TXDESC_SIZE) % 512) == 0)
blnSetTxDescOffset = 1;
else
blnSetTxDescOffset = 0;
} else {
if (((sz + TXDESC_SIZE) % 64) == 0)
blnSetTxDescOffset = 1;
else
blnSetTxDescOffset = 0;
}
return blnSetTxDescOffset;
}
static void rtl8192cu_cal_txdesc_chksum(struct tx_desc *ptxdesc)
{
__le16 *usPtr = (__le16 *)ptxdesc;
u32 count = 16; /* (32 bytes / 2 bytes per XOR) => 16 times */
u32 index;
u16 checksum = 0;
/* Clear first */
ptxdesc->txdw7 &= cpu_to_le32(0xffff0000);
for (index = 0 ; index < count ; index++)
checksum = checksum ^ le16_to_cpu(*(usPtr + index));
ptxdesc->txdw7 |= cpu_to_le32(0x0000ffff&checksum);
}
static void fill_txdesc_sectype(struct pkt_attrib *pattrib, struct tx_desc *ptxdesc)
{
if ((pattrib->encrypt > 0) && !pattrib->bswenc) {
switch (pattrib->encrypt) {
/* SEC_TYPE */
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
ptxdesc->txdw1 |= cpu_to_le32((0x01<<22)&0x00c00000);
break;
case WLAN_CIPHER_SUITE_TKIP:
/* ptxdesc->txdw1 |= cpu_to_le32((0x02<<22)&0x00c00000); */
ptxdesc->txdw1 |= cpu_to_le32((0x01<<22)&0x00c00000);
break;
case WLAN_CIPHER_SUITE_CCMP:
ptxdesc->txdw1 |= cpu_to_le32((0x03<<22)&0x00c00000);
break;
case 0:
default:
break;
}
}
}
static void fill_txdesc_vcs(struct pkt_attrib *pattrib, __le32 *pdw)
{
/* DBG_8723A("cvs_mode =%d\n", pattrib->vcs_mode); */
switch (pattrib->vcs_mode) {
case RTS_CTS:
*pdw |= cpu_to_le32(BIT(12));
break;
case CTS_TO_SELF:
*pdw |= cpu_to_le32(BIT(11));
break;
case NONE_VCS:
default:
break;
}
if (pattrib->vcs_mode) {
*pdw |= cpu_to_le32(BIT(13));
/* Set RTS BW */
if (pattrib->ht_en) {
*pdw |= (pattrib->bwmode&HT_CHANNEL_WIDTH_40) ? cpu_to_le32(BIT(27)) : 0;
if (pattrib->ch_offset == HAL_PRIME_CHNL_OFFSET_LOWER)
*pdw |= cpu_to_le32((0x01<<28)&0x30000000);
else if (pattrib->ch_offset == HAL_PRIME_CHNL_OFFSET_UPPER)
*pdw |= cpu_to_le32((0x02<<28)&0x30000000);
else if (pattrib->ch_offset == HAL_PRIME_CHNL_OFFSET_DONT_CARE)
*pdw |= 0;
else
*pdw |= cpu_to_le32((0x03<<28)&0x30000000);
}
}
}
static void fill_txdesc_phy(struct pkt_attrib *pattrib, __le32 *pdw)
{
if (pattrib->ht_en) {
*pdw |= (pattrib->bwmode&HT_CHANNEL_WIDTH_40) ? cpu_to_le32(BIT(25)) : 0;
if (pattrib->ch_offset == HAL_PRIME_CHNL_OFFSET_LOWER)
*pdw |= cpu_to_le32((0x01<<20)&0x003f0000);
else if (pattrib->ch_offset == HAL_PRIME_CHNL_OFFSET_UPPER)
*pdw |= cpu_to_le32((0x02<<20)&0x003f0000);
else if (pattrib->ch_offset == HAL_PRIME_CHNL_OFFSET_DONT_CARE)
*pdw |= 0;
else
*pdw |= cpu_to_le32((0x03<<20)&0x003f0000);
}
}
static s32 update_txdesc(struct xmit_frame *pxmitframe, u8 *pmem, s32 sz)
{
int pull = 0;
uint qsel;
struct rtw_adapter *padapter = pxmitframe->padapter;
struct pkt_attrib *pattrib = &pxmitframe->attrib;
struct hal_data_8723a *pHalData = GET_HAL_DATA(padapter);
struct dm_priv *pdmpriv = &pHalData->dmpriv;
struct tx_desc *ptxdesc = (struct tx_desc *)pmem;
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &pmlmeext->mlmext_info;
int bmcst = is_multicast_ether_addr(pattrib->ra);
if (urb_zero_packet_chk(padapter, sz) == 0) {
ptxdesc = (struct tx_desc *)(pmem+PACKET_OFFSET_SZ);
pull = 1;
pxmitframe->pkt_offset--;
}
memset(ptxdesc, 0, sizeof(struct tx_desc));
if (pxmitframe->frame_tag == DATA_FRAMETAG) {
/* offset 4 */
ptxdesc->txdw1 |= cpu_to_le32(pattrib->mac_id&0x1f);
qsel = (uint)(pattrib->qsel & 0x0000001f);
ptxdesc->txdw1 |= cpu_to_le32((qsel << QSEL_SHT) & 0x00001f00);
ptxdesc->txdw1 |= cpu_to_le32((pattrib->raid<<16) & 0x000f0000);
fill_txdesc_sectype(pattrib, ptxdesc);
if (pattrib->ampdu_en)
ptxdesc->txdw1 |= cpu_to_le32(BIT(5));/* AGG EN */
else
ptxdesc->txdw1 |= cpu_to_le32(BIT(6));/* AGG BK */
/* offset 8 */
/* offset 12 */
ptxdesc->txdw3 |= cpu_to_le32((pattrib->seqnum<<16)&0xffff0000);
/* offset 16 , offset 20 */
if (pattrib->qos_en)
ptxdesc->txdw4 |= cpu_to_le32(BIT(6));/* QoS */
if ((pattrib->ether_type != 0x888e) &&
(pattrib->ether_type != 0x0806) &&
(pattrib->dhcp_pkt != 1)) {
/* Non EAP & ARP & DHCP type data packet */
fill_txdesc_vcs(pattrib, &ptxdesc->txdw4);
fill_txdesc_phy(pattrib, &ptxdesc->txdw4);
ptxdesc->txdw4 |= cpu_to_le32(0x00000008);/* RTS Rate = 24M */
ptxdesc->txdw5 |= cpu_to_le32(0x0001ff00);/* */
/* use REG_INIDATA_RATE_SEL value */
ptxdesc->txdw5 |= cpu_to_le32(pdmpriv->INIDATA_RATE[pattrib->mac_id]);
} else {
/* EAP data packet and ARP packet. */
/* Use the 1M data rate to send the EAP/ARP packet. */
/* This will maybe make the handshake smooth. */
ptxdesc->txdw1 |= cpu_to_le32(BIT(6));/* AGG BK */
ptxdesc->txdw4 |= cpu_to_le32(BIT(8));/* driver uses rate */
if (pmlmeinfo->preamble_mode == PREAMBLE_SHORT)
ptxdesc->txdw4 |= cpu_to_le32(BIT(24));/* DATA_SHORT */
ptxdesc->txdw5 |= cpu_to_le32(MRateToHwRate23a(pmlmeext->tx_rate));
}
} else if (pxmitframe->frame_tag == MGNT_FRAMETAG) {
/* offset 4 */
ptxdesc->txdw1 |= cpu_to_le32(pattrib->mac_id&0x1f);
qsel = (uint)(pattrib->qsel&0x0000001f);
ptxdesc->txdw1 |= cpu_to_le32((qsel<<QSEL_SHT)&0x00001f00);
ptxdesc->txdw1 |= cpu_to_le32((pattrib->raid<<16) & 0x000f0000);
/* offset 8 */
/* CCX-TXRPT ack for xmit mgmt frames. */
if (pxmitframe->ack_report)
ptxdesc->txdw2 |= cpu_to_le32(BIT(19));
/* offset 12 */
ptxdesc->txdw3 |= cpu_to_le32((pattrib->seqnum<<16)&0xffff0000);
/* offset 16 */
ptxdesc->txdw4 |= cpu_to_le32(BIT(8));/* driver uses rate */
/* offset 20 */
ptxdesc->txdw5 |= cpu_to_le32(BIT(17));/* retry limit enable */
ptxdesc->txdw5 |= cpu_to_le32(0x00180000);/* retry limit = 6 */
ptxdesc->txdw5 |= cpu_to_le32(MRateToHwRate23a(pmlmeext->tx_rate));
} else if (pxmitframe->frame_tag == TXAGG_FRAMETAG) {
DBG_8723A("pxmitframe->frame_tag == TXAGG_FRAMETAG\n");
} else {
DBG_8723A("pxmitframe->frame_tag = %d\n",
pxmitframe->frame_tag);
/* offset 4 */
ptxdesc->txdw1 |= cpu_to_le32((4)&0x1f);/* CAM_ID(MAC_ID) */
ptxdesc->txdw1 |= cpu_to_le32((6<<16) & 0x000f0000);/* raid */
/* offset 8 */
/* offset 12 */
ptxdesc->txdw3 |= cpu_to_le32((pattrib->seqnum<<16)&0xffff0000);
/* offset 16 */
ptxdesc->txdw4 |= cpu_to_le32(BIT(8));/* driver uses rate */
/* offset 20 */
ptxdesc->txdw5 |= cpu_to_le32(MRateToHwRate23a(pmlmeext->tx_rate));
}
/* (1) The sequence number of each non-Qos frame / broadcast / multicast / */
/* mgnt frame should be controlled by Hw because Fw will also send null data */
/* which we cannot control when Fw LPS enable. */
/* --> default enable non-Qos data sequense number. 2010.06.23. by tynli. */
/* (2) Enable HW SEQ control for beacon packet, because we use Hw beacon. */
/* (3) Use HW Qos SEQ to control the seq num of Ext port non-Qos packets. */
if (!pattrib->qos_en) {
/* Hw set sequence number */
ptxdesc->txdw4 |= cpu_to_le32(BIT(7));
/* set bit3 to 1. */
ptxdesc->txdw3 |= cpu_to_le32((8 << 28));
}
/* offset 0 */
ptxdesc->txdw0 |= cpu_to_le32(sz&0x0000ffff);
ptxdesc->txdw0 |= cpu_to_le32(OWN | FSG | LSG);
ptxdesc->txdw0 |= cpu_to_le32(((TXDESC_SIZE+OFFSET_SZ)<<OFFSET_SHT)&0x00ff0000);/* 32 bytes for TX Desc */
if (bmcst)
ptxdesc->txdw0 |= cpu_to_le32(BIT(24));
RT_TRACE(_module_rtl871x_xmit_c_, _drv_info_,
"offset0-txdesc = 0x%x\n", ptxdesc->txdw0);
/* offset 4 */
/* pkt_offset, unit:8 bytes padding */
if (pxmitframe->pkt_offset > 0)
ptxdesc->txdw1 |= cpu_to_le32((pxmitframe->pkt_offset << 26) & 0x7c000000);
rtl8192cu_cal_txdesc_chksum(ptxdesc);
return pull;
}
static int rtw_dump_xframe(struct rtw_adapter *padapter,
struct xmit_frame *pxmitframe)
{
int ret = _SUCCESS;
int inner_ret = _SUCCESS;
int t, sz, w_sz, pull = 0;
u8 *mem_addr;
u32 ff_hwaddr;
struct xmit_buf *pxmitbuf = pxmitframe->pxmitbuf;
struct pkt_attrib *pattrib = &pxmitframe->attrib;
struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
if (pxmitframe->frame_tag == DATA_FRAMETAG &&
pxmitframe->attrib.ether_type != ETH_P_ARP &&
pxmitframe->attrib.ether_type != ETH_P_PAE &&
pxmitframe->attrib.dhcp_pkt != 1)
rtw_issue_addbareq_cmd23a(padapter, pxmitframe);
mem_addr = pxmitframe->buf_addr;
RT_TRACE(_module_rtl871x_xmit_c_, _drv_info_, "rtw_dump_xframe()\n");
for (t = 0; t < pattrib->nr_frags; t++) {
if (inner_ret != _SUCCESS && ret == _SUCCESS)
ret = _FAIL;
if (t != (pattrib->nr_frags - 1)) {
RT_TRACE(_module_rtl871x_xmit_c_, _drv_err_,
"pattrib->nr_frags =%d\n", pattrib->nr_frags);
sz = pxmitpriv->frag_len;
sz = sz - 4 - pattrib->icv_len;
} else {
/* no frag */
sz = pattrib->last_txcmdsz;
}
pull = update_txdesc(pxmitframe, mem_addr, sz);
if (pull) {
mem_addr += PACKET_OFFSET_SZ; /* pull txdesc head */
pxmitframe->buf_addr = mem_addr;
w_sz = sz + TXDESC_SIZE;
} else {
w_sz = sz + TXDESC_SIZE + PACKET_OFFSET_SZ;
}
ff_hwaddr = rtw_get_ff_hwaddr23a(pxmitframe);
inner_ret = rtl8723au_write_port(padapter, ff_hwaddr,
w_sz, pxmitbuf);
rtw_count_tx_stats23a(padapter, pxmitframe, sz);
RT_TRACE(_module_rtl871x_xmit_c_, _drv_info_,
"rtw_write_port, w_sz =%d\n", w_sz);
mem_addr += w_sz;
mem_addr = PTR_ALIGN(mem_addr, 4);
}
rtw_free_xmitframe23a(pxmitpriv, pxmitframe);
if (ret != _SUCCESS)
rtw23a_sctx_done_err(&pxmitbuf->sctx, RTW_SCTX_DONE_UNKNOWN);
return ret;
}
bool rtl8723au_xmitframe_complete(struct rtw_adapter *padapter,
struct xmit_priv *pxmitpriv,
struct xmit_buf *pxmitbuf)
{
struct hw_xmit *phwxmits;
struct xmit_frame *pxmitframe;
int hwentry;
int res = _SUCCESS, xcnt = 0;
phwxmits = pxmitpriv->hwxmits;
hwentry = pxmitpriv->hwxmit_entry;
RT_TRACE(_module_rtl871x_xmit_c_, _drv_info_, "xmitframe_complete()\n");
if (pxmitbuf == NULL) {
pxmitbuf = rtw_alloc_xmitbuf23a(pxmitpriv);
if (!pxmitbuf)
return false;
}
pxmitframe = rtw_dequeue_xframe23a(pxmitpriv, phwxmits, hwentry);
if (pxmitframe) {
pxmitframe->pxmitbuf = pxmitbuf;
pxmitframe->buf_addr = pxmitbuf->pbuf;
pxmitbuf->priv_data = pxmitframe;
if (pxmitframe->frame_tag == DATA_FRAMETAG) {
if (pxmitframe->attrib.priority <= 15)/* TID0~15 */
res = rtw_xmitframe_coalesce23a(padapter, pxmitframe->pkt, pxmitframe);
rtw_os_xmit_complete23a(padapter, pxmitframe);/* always return ndis_packet after rtw_xmitframe_coalesce23a */
}
RT_TRACE(_module_rtl871x_xmit_c_, _drv_info_,
"xmitframe_complete(): rtw_dump_xframe\n");
if (res == _SUCCESS) {
rtw_dump_xframe(padapter, pxmitframe);
} else {
rtw_free_xmitbuf23a(pxmitpriv, pxmitbuf);
rtw_free_xmitframe23a(pxmitpriv, pxmitframe);
}
xcnt++;
} else {
rtw_free_xmitbuf23a(pxmitpriv, pxmitbuf);
return false;
}
return true;
}
static int xmitframe_direct(struct rtw_adapter *padapter,
struct xmit_frame *pxmitframe)
{
int res;
res = rtw_xmitframe_coalesce23a(padapter, pxmitframe->pkt, pxmitframe);
if (res == _SUCCESS)
rtw_dump_xframe(padapter, pxmitframe);
return res;
}
/*
* Return
* true dump packet directly
* false enqueue packet
*/
bool rtl8723au_hal_xmit(struct rtw_adapter *padapter,
struct xmit_frame *pxmitframe)
{
int res;
struct xmit_buf *pxmitbuf = NULL;
struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
struct pkt_attrib *pattrib = &pxmitframe->attrib;
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
pattrib->qsel = pattrib->priority;
spin_lock_bh(&pxmitpriv->lock);
#ifdef CONFIG_8723AU_AP_MODE
if (xmitframe_enqueue_for_sleeping_sta23a(padapter, pxmitframe)) {
struct sta_info *psta;
struct sta_priv *pstapriv = &padapter->stapriv;
spin_unlock_bh(&pxmitpriv->lock);
if (pattrib->psta)
psta = pattrib->psta;
else
psta = rtw_get_stainfo23a(pstapriv, pattrib->ra);
if (psta) {
if (psta->sleepq_len > (NR_XMITFRAME>>3))
wakeup_sta_to_xmit23a(padapter, psta);
}
return false;
}
#endif
if (rtw_txframes_sta_ac_pending23a(padapter, pattrib) > 0)
goto enqueue;
if (check_fwstate(pmlmepriv, _FW_UNDER_SURVEY|_FW_UNDER_LINKING) == true)
goto enqueue;
pxmitbuf = rtw_alloc_xmitbuf23a(pxmitpriv);
if (pxmitbuf == NULL)
goto enqueue;
spin_unlock_bh(&pxmitpriv->lock);
pxmitframe->pxmitbuf = pxmitbuf;
pxmitframe->buf_addr = pxmitbuf->pbuf;
pxmitbuf->priv_data = pxmitframe;
if (xmitframe_direct(padapter, pxmitframe) != _SUCCESS) {
rtw_free_xmitbuf23a(pxmitpriv, pxmitbuf);
rtw_free_xmitframe23a(pxmitpriv, pxmitframe);
}
return true;
enqueue:
res = rtw_xmitframe_enqueue23a(padapter, pxmitframe);
spin_unlock_bh(&pxmitpriv->lock);
if (res != _SUCCESS) {
RT_TRACE(_module_xmit_osdep_c_, _drv_err_,
"pre_xmitframe: enqueue xmitframe fail\n");
rtw_free_xmitframe23a(pxmitpriv, pxmitframe);
/* Trick, make the statistics correct */
pxmitpriv->tx_pkts--;
pxmitpriv->tx_drop++;
return true;
}
return false;
}
int rtl8723au_mgnt_xmit(struct rtw_adapter *padapter,
struct xmit_frame *pmgntframe)
{
return rtw_dump_xframe(padapter, pmgntframe);
}
int rtl8723au_hal_xmitframe_enqueue(struct rtw_adapter *padapter,
struct xmit_frame *pxmitframe)
{
struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
int err;
err = rtw_xmitframe_enqueue23a(padapter, pxmitframe);
if (err != _SUCCESS) {
rtw_free_xmitframe23a(pxmitpriv, pxmitframe);
/* Trick, make the statistics correct */
pxmitpriv->tx_pkts--;
pxmitpriv->tx_drop++;
} else {
tasklet_hi_schedule(&pxmitpriv->xmit_tasklet);
}
return err;
}