blob: e7599b9500056e70eda35cbd42cb2180a044186a [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2015 - 2017 Realtek Corporation.
*
* 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 _RTL8822BS_RECV_C_
#include <drv_types.h> /* PADAPTER and etc. */
#include <hal_data.h> /* HAL_DATA_TYPE */
#include "../../hal_halmac.h" /* BIT_ACRC32_8822B and etc. */
#include "../rtl8822b.h" /* rtl8822b_rxdesc2attribute(), rtl8822b_c2h_handler_no_io() */
static s32 initrecvbuf(struct recv_buf *precvbuf, PADAPTER adapter)
{
_rtw_init_listhead(&precvbuf->list);
_rtw_spinlock_init(&precvbuf->recvbuf_lock);
precvbuf->adapter = adapter;
return _SUCCESS;
}
static void freerecvbuf(struct recv_buf *precvbuf)
{
_rtw_spinlock_free(&precvbuf->recvbuf_lock);
}
static void start_rx_handle(PADAPTER p)
{
#ifdef CONFIG_RECV_THREAD_MODE
_rtw_up_sema(&p->recvpriv.recv_sema);
#else
#ifdef PLATFORM_LINUX
tasklet_schedule(&p->recvpriv.recv_tasklet);
#endif
#endif
}
static void stop_rx_handle(PADAPTER p)
{
#ifdef CONFIG_RECV_THREAD_MODE
#else
#ifdef PLATFORM_LINUX
tasklet_kill(&p->recvpriv.recv_tasklet);
#endif
#endif
}
/*
* Return:
* Pointer of _pkt, otherwise NULL.
*/
static _pkt *alloc_recvbuf_skb(struct recv_buf *recvbuf, u32 size)
{
_pkt *skb;
u32 alignsz = RECVBUFF_ALIGN_SZ;
#ifdef PLATFORM_LINUX
SIZE_PTR tmpaddr = 0;
SIZE_PTR alignment = 0;
#endif /* PLATFORM_LINUX */
size += alignsz;
skb = rtw_skb_alloc(size);
if (!skb) {
RTW_WARN("%s: alloc_skb fail! size=%d\n", __FUNCTION__, size);
return NULL;
}
#ifdef PLATFORM_LINUX
skb->dev = recvbuf->adapter->pnetdev;
tmpaddr = (SIZE_PTR)skb->data;
alignment = tmpaddr & (alignsz - 1);
skb_reserve(skb, alignsz - alignment);
#endif /* PLATFORM_LINUX */
recvbuf->pskb = skb;
return skb;
}
/*
* Description:
* Allocate skb for recv_buf, the size is MAX_RECVBUF_SZ
*
* Parameters:
* recvbuf pointer of struct recv_buf
* size skb size, only valid when NOT define CONFIG_SDIO_RX_COPY.
* If CONFIG_SDIO_RX_COPY, size always be MAX_RECVBUF_SZ.
*
* Return:
* Pointer of _pkt, otherwise NULL.
*/
_pkt *rtl8822bs_alloc_recvbuf_skb(struct recv_buf *recvbuf, u32 size)
{
_pkt *skb;
skb = recvbuf->pskb;
#ifdef CONFIG_SDIO_RX_COPY
if (skb) {
skb_reset_tail_pointer(skb);
skb->len = 0;
return skb;
}
RTW_WARN("%s: skb not exist in recv_buf!\n", __FUNCTION__);
size = MAX_RECVBUF_SZ;
#else /* !CONFIG_SDIO_RX_COPY */
if (skb) {
RTW_WARN("%s: skb already exist in recv_buf!\n", __FUNCTION__);
rtl8822bs_free_recvbuf_skb(recvbuf);
}
#endif /* !CONFIG_SDIO_RX_COPY */
skb = alloc_recvbuf_skb(recvbuf, size);
if (!skb)
return NULL;
return skb;
}
static void free_recvbuf_skb(struct recv_buf *recvbuf)
{
_pkt *skb;
skb = recvbuf->pskb;
if (!skb)
return;
recvbuf->pskb = NULL;
rtw_skb_free(skb);
}
void rtl8822bs_free_recvbuf_skb(struct recv_buf *recvbuf)
{
#ifndef CONFIG_SDIO_RX_COPY
free_recvbuf_skb(recvbuf);
#endif /* !CONFIG_SDIO_RX_COPY */
}
/*
* Return:
* _SUCCESS Allocate resource OK.
* _FAIL Fail to allocate resource.
*/
static inline s32 os_recvbuf_resource_alloc(PADAPTER adapter, struct recv_buf *recvbuf)
{
s32 ret = _SUCCESS;
#ifdef CONFIG_SDIO_RX_COPY
alloc_recvbuf_skb(recvbuf, MAX_RECVBUF_SZ);
#endif /* CONFIG_SDIO_RX_COPY */
return ret;
}
static inline void os_recvbuf_resource_free(PADAPTER adapter, struct recv_buf *recvbuf)
{
#ifdef CONFIG_SDIO_RX_COPY
free_recvbuf_skb(recvbuf);
#endif /* CONFIG_SDIO_RX_COPY */
}
#if 0
static union recv_frame *copy_recvframe(union recv_frame *recvframe, PADAPTER adapter)
{
PHAL_DATA_TYPE hal;
struct recv_priv *precvpriv;
_queue *pfree_recv_queue;
struct rx_pkt_attrib *attrib = NULL;
union recv_frame *copyframe = NULL;
_pkt *copypkt = NULL;
hal = GET_HAL_DATA(adapter);
precvpriv = &adapter->recvpriv;
pfree_recv_queue = &precvpriv->free_recv_queue;
attrib = &recvframe->u.hdr.attrib;
copyframe = rtw_alloc_recvframe(pfree_recv_queue);
if (!copyframe) {
RTW_INFO(FUNC_ADPT_FMT ": Alloc recvframe FAIL!\n",
FUNC_ADPT_ARG(adapter));
return NULL;
}
copyframe->u.hdr.adapter = adapter;
_rtw_memcpy(&copyframe->u.hdr.attrib, attrib, sizeof(struct rx_pkt_attrib));
#if 0
/*
* driver need to set skb len for skb_copy().
* If skb->len is zero, skb_copy() will not copy data from original skb.
*/
skb_put(recvframe->u.hdr.pkt, attrib->pkt_len);
#else
RTW_INFO(FUNC_ADPT_FMT ": skb len=%d!\n",
FUNC_ADPT_ARG(adapter), recvframe->u.hdr.pkt->len);
#endif
copypkt = rtw_skb_copy(recvframe->u.hdr.pkt);
if (!copypkt) {
if ((attrib->mfrag == 1) && (attrib->frag_num == 0)) {
RTW_ERR(FUNC_ADPT_FMT ": rtw_skb_copy fail for first fragment!\n",
FUNC_ADPT_ARG(adapter));
rtw_free_recvframe(recvframe, &precvpriv->free_recv_queue);
return NULL;
}
copypkt = rtw_skb_clone(recvframe->u.hdr.pkt);
if (!copypkt) {
RTW_ERR(FUNC_ADPT_FMT ": rtw_skb_clone fail, drop frame!\n",
FUNC_ADPT_ARG(adapter));
rtw_free_recvframe(recvframe, &precvpriv->free_recv_queue);
return NULL;
}
}
copypkt->dev = adapter->pnetdev;
copyframe->u.hdr.pkt = copypkt;
copyframe->u.hdr.len = copypkt->len;
copyframe->u.hdr.rx_head = copypkt->head;
copyframe->u.hdr.rx_data = copypkt->data;
copyframe->u.hdr.rx_tail = skb_tail_pointer(copypkt);
copyframe->u.hdr.rx_end = skb_end_pointer(copypkt);
return copyframe;
}
#endif
/*
* Return:
* _SUCCESS OK to send packet
* _FAIL FAIL to send packet
*/
static s32 recv_entry(union recv_frame *recvframe, u8 *phy_status)
{
s32 ret = _SUCCESS;
PADAPTER adapter;
struct rx_pkt_attrib *attrib = NULL;
#ifdef CONFIG_CONCURRENT_MODE
struct dvobj_priv *d;
u8 *addr1, *macaddr;
u8 mcast, i;
union recv_frame *copyframe = NULL;
#endif /* CONFIG_CONCURRENT_MODE */
attrib = &recvframe->u.hdr.attrib;
#if 0
d = adapter_to_dvobj(recvframe->u.hdr.adapter);
addr1 = GetAddr1Ptr(recvframe->u.hdr.rx_data);
mcast = IS_MCAST(addr1);
if (_TRUE == mcast) {
/* BC/MC packets */
for (i = 1; i < d->iface_nums; i++) {
adapter = d->adapters[i];
if (rtw_if_up(adapter) == _FALSE)
continue;
copyframe = copy_recvframe(recvframe, adapter);
if (!copyframe)
break;
if (attrib->physt)
rx_query_phy_status(copyframe, phy_status);
ret = rtw_recv_entry(copyframe);
}
} else {
/* unicast packets */
for (i = 0; i < d->iface_nums; i++) {
adapter = d->adapters[i];
if (rtw_if_up(adapter) == _FALSE)
continue;
macaddr = adapter_mac_addr(adapter);
if (_rtw_memcmp(addr1, macaddr, ETH_ALEN) == _FALSE)
continue;
/* change to target interface */
recvframe->u.hdr.adapter = adapter;
recvframe->u.hdr.pkt->dev = adapter->pnetdev;
break;
}
}
#else
ret = pre_recv_entry(recvframe, attrib->physt ? phy_status : NULL);
#endif
return ret;
}
/*
* Return:
* _TRUE Finish preparing recv_frame
* _FALSE Something fail to prepare recv_frame
*/
static _pkt *prepare_recvframe_pkt(struct recv_buf *recvbuf, union recv_frame *recvframe)
{
_pkt *pkt = NULL;
struct rx_pkt_attrib *attrib;
u32 desc_size;
u32 skb_len;
u8 *data;
#ifdef CONFIG_SDIO_RX_COPY
u32 shift_sz, alloc_sz;
#endif /* CONFIG_SDIO_RX_COPY */
pkt = recvframe->u.hdr.pkt;
if (pkt) {
RTW_WARN("%s: recvframe pkt already exist!\n", __FUNCTION__);
return pkt;
}
desc_size = rtl8822b_get_rx_desc_size(recvbuf->adapter);
attrib = &recvframe->u.hdr.attrib;
skb_len = attrib->pkt_len;
if (rtl8822b_rx_fcs_appended(recvbuf->adapter))
skb_len -= IEEE80211_FCS_LEN;
data = recvbuf->pdata + desc_size + attrib->drvinfo_sz;
#if 0
data += attrib->shift_sz;
#endif
#ifdef CONFIG_SDIO_RX_COPY
/* For 8 bytes IP header alignment. */
if (attrib->qos)
/* Qos data, wireless lan header length is 26 */
shift_sz = 6;
else
shift_sz = 0;
/*
* For first fragment packet, driver need allocate
* (1536 + drvinfo_sz + RXDESC_SIZE) to defrag packet.
* In 8822B, drvinfo_sz = 32, RXDESC_SIZE = 24, 1536 + 32 + 24 = 1592.
* And need 8 is for skb->data 8 bytes alignment.
* Round (1536 + 24 + 32 + shift_sz + 8) to 128 bytes alignment,
* and finally get 1664.
*/
if ((attrib->mfrag == 1) && (attrib->frag_num == 0)) {
if (skb_len <= 1650)
alloc_sz = 1664;
else
alloc_sz = skb_len + 14;
} else {
alloc_sz = skb_len;
/*
* 6 is for IP header 8 bytes alignment in QoS packet case.
* 8 is for skb->data 4 bytes alignment.
*/
alloc_sz += 14;
}
pkt = rtw_skb_alloc(alloc_sz);
if (pkt) {
pkt->dev = recvframe->u.hdr.adapter->pnetdev;
/* force pkt->data at 8-byte alignment address */
skb_reserve(pkt, 8 - ((SIZE_PTR)pkt->data & 7));
/* force ip_hdr at 8-byte alignment address according to shift_sz. */
skb_reserve(pkt, shift_sz);
_rtw_memcpy(skb_put(pkt, skb_len), data, skb_len);
} else if ((attrib->mfrag == 1) && (attrib->frag_num == 0)) {
RTW_ERR("%s: alloc_skb fail for first fragement\n", __FUNCTION__);
return NULL;
}
#endif /* CONFIG_SDIO_RX_COPY */
if (!pkt) {
pkt = rtw_skb_clone(recvbuf->pskb);
if (!pkt) {
RTW_ERR("%s: rtw_skb_clone fail\n", __FUNCTION__);
return NULL;
}
pkt->data = data;
skb_set_tail_pointer(pkt, skb_len);
pkt->len = skb_len;
}
recvframe->u.hdr.pkt = pkt;
recvframe->u.hdr.len = pkt->len;
recvframe->u.hdr.rx_head = pkt->head;
recvframe->u.hdr.rx_data = pkt->data;
recvframe->u.hdr.rx_tail = skb_tail_pointer(pkt);
recvframe->u.hdr.rx_end = skb_end_pointer(pkt);
return pkt;
}
/*
* Return:
* _SUCCESS Finish processing recv_buf
* others Something fail to process recv_buf
*/
static u8 recvbuf_handler(struct recv_buf *recvbuf)
{
PADAPTER p;
struct recv_priv *recvpriv;
union recv_frame *recvframe;
struct rx_pkt_attrib *attrib;
u32 desc_size;
_pkt *pkt;
u32 rx_report_sz, pkt_offset, pkt_len;
u8 *ptr;
u8 ret = _SUCCESS;
p = recvbuf->adapter;
recvpriv = &p->recvpriv;
ptr = recvbuf->pdata;
desc_size = rtl8822b_get_rx_desc_size(p);
while (ptr < recvbuf->ptail) {
recvframe = rtw_alloc_recvframe(&recvpriv->free_recv_queue);
if (!recvframe) {
RTW_WARN("%s: no enough recv frame!\n", __FUNCTION__);
ret = RTW_RFRAME_UNAVAIL;
break;
}
/* rx desc parsing */
attrib = &recvframe->u.hdr.attrib;
rtl8822b_rxdesc2attribute(attrib, ptr);
/* drop recvbuf if pkt_len of rx desc is too small */
pkt_len = attrib->pkt_len;
if (pkt_len && rtl8822b_rx_fcs_appended(recvbuf->adapter)) {
if (pkt_len > IEEE80211_FCS_LEN)
pkt_len -= IEEE80211_FCS_LEN;
else
pkt_len = 0;
}
if (pkt_len == 0) {
RTW_WARN("%s: pkt len(%u) is too small, skip!\n",
__FUNCTION__, attrib->pkt_len);
rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
break;
}
rx_report_sz = desc_size + attrib->drvinfo_sz;
pkt_offset = rx_report_sz + attrib->shift_sz + attrib->pkt_len;
if ((ptr + pkt_offset) > recvbuf->ptail) {
RTW_WARN("%s: next pkt len(%p,%d) exceed ptail(%p)!\n",
__FUNCTION__, ptr, pkt_offset, recvbuf->ptail);
rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
break;
}
/* fix Hardware RX data error, drop whole recv_buffer */
if (!rtw_hal_rcr_check(p, BIT_ACRC32_8822B)
&& attrib->crc_err) {
RTW_WARN("%s: Received unexpected CRC error packet!!\n", __FUNCTION__);
rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
break;
}
if ((attrib->crc_err) || (attrib->icv_err)) {
#ifdef CONFIG_MP_INCLUDED
if (p->registrypriv.mp_mode == 1) {
if (check_fwstate(&p->mlmepriv, WIFI_MP_STATE) == _TRUE) {
if (attrib->crc_err == 1)
p->mppriv.rx_crcerrpktcount++;
}
} else
#endif /* CONFIG_MP_INCLUDED */
{
RTW_INFO("%s: crc_err=%d icv_err=%d, skip!\n",
__FUNCTION__, attrib->crc_err, attrib->icv_err);
}
rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
} else {
pkt = prepare_recvframe_pkt(recvbuf, recvframe);
if (!pkt) {
rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
ret = RTW_RFRAME_PKT_UNAVAIL;
break;
}
/* move to start of PHY_STATUS */
ptr += desc_size;
if (rtl8822b_rx_ba_ssn_appended(p))
ptr += RTW_HALMAC_BA_SSN_RPT_SIZE;
recv_entry(recvframe, ptr);
}
pkt_offset = _RND8(pkt_offset);
recvbuf->pdata += pkt_offset;
ptr = recvbuf->pdata;
}
return ret;
}
s32 rtl8822bs_recv_hdl(_adapter *adapter)
{
struct recv_priv *recvpriv;
struct recv_buf *recvbuf;
u8 c2h = 0;
s32 ret = _SUCCESS;
recvpriv = &adapter->recvpriv;
#ifdef CONFIG_RTW_NAPI_DYNAMIC
if (adapter->registrypriv.en_napi) {
struct dvobj_priv *d;
struct registry_priv *registry;
d = adapter_to_dvobj(adapter);
registry = &adapter->registrypriv;
if (d->traffic_stat.cur_rx_tp > registry->napi_threshold)
d->en_napi_dynamic = 1;
else
d->en_napi_dynamic = 0;
}
#endif /* CONFIG_RTW_NAPI_DYNAMIC */
do {
recvbuf = rtw_dequeue_recvbuf(&recvpriv->recv_buf_pending_queue);
if (NULL == recvbuf)
break;
c2h = GET_RX_DESC_C2H_8822B(recvbuf->pdata);
if (c2h)
rtl8822b_c2h_handler_no_io(adapter, recvbuf->pdata, recvbuf->len);
else
ret = recvbuf_handler(recvbuf);
if (_SUCCESS != ret) {
rtw_enqueue_recvbuf_to_head(recvbuf, &recvpriv->recv_buf_pending_queue);
break;
}
/* free recv_buf */
rtl8822bs_free_recvbuf_skb(recvbuf);
rtw_enqueue_recvbuf(recvbuf, &recvpriv->free_recv_buf_queue);
} while (1);
#ifdef CONFIG_RTW_NAPI
#ifdef CONFIG_RTW_NAPI_V2
if (adapter->registrypriv.en_napi) {
struct dvobj_priv *d;
struct _ADAPTER *a;
u8 i;
d = adapter_to_dvobj(adapter);
for (i = 0; i < d->iface_nums; i++) {
a = d->padapters[i];
recvpriv = &a->recvpriv;
if ((rtw_if_up(a) == _TRUE)
&& skb_queue_len(&recvpriv->rx_napi_skb_queue))
napi_schedule(&a->napi);
}
}
#endif /* CONFIG_RTW_NAPI_V2 */
#endif /* CONFIG_RTW_NAPI */
return ret;
}
static void recv_tasklet(void *priv)
{
PADAPTER adapter;
s32 ret;
adapter = (PADAPTER)priv;
ret = rtl8822bs_recv_hdl(adapter);
if (ret == RTW_RFRAME_UNAVAIL
|| ret == RTW_RFRAME_PKT_UNAVAIL)
start_rx_handle(adapter);
}
/*
* Initialize recv private variable for hardware dependent
* 1. recv buf
* 2. recv tasklet
*/
s32 rtl8822bs_init_recv_priv(PADAPTER adapter)
{
s32 res;
u32 i, n;
struct recv_priv *precvpriv;
struct recv_buf *precvbuf;
res = _SUCCESS;
precvpriv = &adapter->recvpriv;
/* 1. init recv buffer */
_rtw_init_queue(&precvpriv->free_recv_buf_queue);
_rtw_init_queue(&precvpriv->recv_buf_pending_queue);
n = NR_RECVBUFF * sizeof(struct recv_buf) + 4;
precvpriv->pallocated_recv_buf = rtw_zmalloc(n);
if (precvpriv->pallocated_recv_buf == NULL) {
res = _FAIL;
goto exit;
}
precvpriv->precv_buf = (u8 *)N_BYTE_ALIGMENT((SIZE_PTR)(precvpriv->pallocated_recv_buf), 4);
/* init each recv buffer */
precvbuf = (struct recv_buf *)precvpriv->precv_buf;
for (i = 0; i < NR_RECVBUFF; i++) {
res = initrecvbuf(precvbuf, adapter);
if (res == _FAIL)
break;
res = rtw_os_recvbuf_resource_alloc(adapter, precvbuf);
if (res == _FAIL) {
freerecvbuf(precvbuf);
break;
}
res = os_recvbuf_resource_alloc(adapter, precvbuf);
if (res == _FAIL) {
freerecvbuf(precvbuf);
break;
}
rtw_list_insert_tail(&precvbuf->list, &precvpriv->free_recv_buf_queue.queue);
precvbuf++;
}
precvpriv->free_recv_buf_queue_cnt = i;
if (res == _FAIL)
goto initbuferror;
/* 2. init tasklet */
#ifdef PLATFORM_LINUX
tasklet_init(&precvpriv->recv_tasklet,
(void(*)(unsigned long))recv_tasklet,
(unsigned long)adapter);
#endif
goto exit;
initbuferror:
precvbuf = (struct recv_buf *)precvpriv->precv_buf;
if (precvbuf) {
n = precvpriv->free_recv_buf_queue_cnt;
precvpriv->free_recv_buf_queue_cnt = 0;
for (i = 0; i < n ; i++) {
rtw_list_delete(&precvbuf->list);
os_recvbuf_resource_free(adapter, precvbuf);
rtw_os_recvbuf_resource_free(adapter, precvbuf);
freerecvbuf(precvbuf);
precvbuf++;
}
precvpriv->precv_buf = NULL;
}
if (precvpriv->pallocated_recv_buf) {
n = NR_RECVBUFF * sizeof(struct recv_buf) + 4;
rtw_mfree(precvpriv->pallocated_recv_buf, n);
precvpriv->pallocated_recv_buf = NULL;
}
exit:
return res;
}
/*
* Free recv private variable of hardware dependent
* 1. recv buf
* 2. recv tasklet
*/
void rtl8822bs_free_recv_priv(PADAPTER adapter)
{
u32 i, n;
struct recv_priv *precvpriv;
struct recv_buf *precvbuf;
precvpriv = &adapter->recvpriv;
/* 1. kill tasklet */
stop_rx_handle(adapter);
/* 2. free all recv buffers */
precvbuf = (struct recv_buf *)precvpriv->precv_buf;
if (precvbuf) {
n = precvpriv->free_recv_buf_queue_cnt;
precvpriv->free_recv_buf_queue_cnt = 0;
for (i = 0; i < n ; i++) {
rtw_list_delete(&precvbuf->list);
os_recvbuf_resource_free(adapter, precvbuf);
rtw_os_recvbuf_resource_free(adapter, precvbuf);
freerecvbuf(precvbuf);
precvbuf++;
}
precvpriv->precv_buf = NULL;
}
if (precvpriv->pallocated_recv_buf) {
n = NR_RECVBUFF * sizeof(struct recv_buf) + 4;
rtw_mfree(precvpriv->pallocated_recv_buf, n);
precvpriv->pallocated_recv_buf = NULL;
}
}
void rtl8822bs_rxhandler(PADAPTER adapter, struct recv_buf *recvbuf)
{
struct recv_priv *recvpriv;
_queue *pending_queue;
recvpriv = &adapter->recvpriv;
pending_queue = &recvpriv->recv_buf_pending_queue;
/* 1. enqueue recvbuf */
rtw_enqueue_recvbuf(recvbuf, pending_queue);
/* 2. schedule tasklet */
start_rx_handle(adapter);
}