// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
* rtl8712_recv.c
*
* Copyright(c) 2007 - 2010 Realtek Corporation. All rights reserved.
* Linux device driver for RTL8192SU
*
* Modifications for inclusion into the Linux staging tree are
* Copyright(c) 2010 Larry Finger. All rights reserved.
*
* Contact information:
* WLAN FAE <wlanfae@realtek.com>
* Larry Finger <Larry.Finger@lwfinger.net>
*
******************************************************************************/
#define _RTL8712_RECV_C_
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <net/cfg80211.h>
#include "osdep_service.h"
#include "drv_types.h"
#include "recv_osdep.h"
#include "mlme_osdep.h"
#include "ethernet.h"
#include "usb_ops.h"
#include "wifi.h"
static void recv_tasklet(struct tasklet_struct *t);
int r8712_init_recv_priv(struct recv_priv *precvpriv,
struct _adapter *padapter)
{
int i;
struct recv_buf *precvbuf;
addr_t tmpaddr = 0;
int alignment = 0;
struct sk_buff *pskb = NULL;
/*init recv_buf*/
_init_queue(&precvpriv->free_recv_buf_queue);
precvpriv->pallocated_recv_buf =
kzalloc(NR_RECVBUFF * sizeof(struct recv_buf) + 4, GFP_ATOMIC);
if (!precvpriv->pallocated_recv_buf)
return -ENOMEM;
precvpriv->precv_buf = precvpriv->pallocated_recv_buf + 4 -
((addr_t)(precvpriv->pallocated_recv_buf) & 3);
precvbuf = (struct recv_buf *)precvpriv->precv_buf;
for (i = 0; i < NR_RECVBUFF; i++) {
INIT_LIST_HEAD(&precvbuf->list);
spin_lock_init(&precvbuf->recvbuf_lock);
if (r8712_os_recvbuf_resource_alloc(padapter, precvbuf))
break;
precvbuf->ref_cnt = 0;
precvbuf->adapter = padapter;
list_add_tail(&precvbuf->list,
&precvpriv->free_recv_buf_queue.queue);
precvbuf++;
}
precvpriv->free_recv_buf_queue_cnt = NR_RECVBUFF;
tasklet_setup(&precvpriv->recv_tasklet, recv_tasklet);
skb_queue_head_init(&precvpriv->rx_skb_queue);
skb_queue_head_init(&precvpriv->free_recv_skb_queue);
for (i = 0; i < NR_PREALLOC_RECV_SKB; i++) {
pskb = netdev_alloc_skb(padapter->pnetdev, MAX_RECVBUF_SZ +
RECVBUFF_ALIGN_SZ);
if (pskb) {
tmpaddr = (addr_t)pskb->data;
alignment = tmpaddr & (RECVBUFF_ALIGN_SZ - 1);
skb_reserve(pskb, (RECVBUFF_ALIGN_SZ - alignment));
skb_queue_tail(&precvpriv->free_recv_skb_queue, pskb);
}
pskb = NULL;
}
return 0;
}
void r8712_free_recv_priv(struct recv_priv *precvpriv)
{
int i;
struct recv_buf *precvbuf;
struct _adapter *padapter = precvpriv->adapter;
precvbuf = (struct recv_buf *)precvpriv->precv_buf;
for (i = 0; i < NR_RECVBUFF; i++) {
r8712_os_recvbuf_resource_free(padapter, precvbuf);
precvbuf++;
}
kfree(precvpriv->pallocated_recv_buf);
skb_queue_purge(&precvpriv->rx_skb_queue);
if (skb_queue_len(&precvpriv->rx_skb_queue))
netdev_warn(padapter->pnetdev, "r8712u: rx_skb_queue not empty\n");
skb_queue_purge(&precvpriv->free_recv_skb_queue);
if (skb_queue_len(&precvpriv->free_recv_skb_queue))
netdev_warn(padapter->pnetdev, "r8712u: free_recv_skb_queue not empty %d\n",
skb_queue_len(&precvpriv->free_recv_skb_queue));
}
void r8712_init_recvbuf(struct _adapter *padapter, struct recv_buf *precvbuf)
{
precvbuf->transfer_len = 0;
precvbuf->len = 0;
precvbuf->ref_cnt = 0;
if (precvbuf->pbuf) {
precvbuf->pdata = precvbuf->pbuf;
precvbuf->phead = precvbuf->pbuf;
precvbuf->ptail = precvbuf->pbuf;
precvbuf->pend = precvbuf->pdata + MAX_RECVBUF_SZ;
}
}
void r8712_free_recvframe(union recv_frame *precvframe,
struct __queue *pfree_recv_queue)
{
unsigned long irqL;
struct _adapter *padapter = precvframe->u.hdr.adapter;
struct recv_priv *precvpriv = &padapter->recvpriv;
if (precvframe->u.hdr.pkt) {
dev_kfree_skb_any(precvframe->u.hdr.pkt);/*free skb by driver*/
precvframe->u.hdr.pkt = NULL;
}
spin_lock_irqsave(&pfree_recv_queue->lock, irqL);
list_del_init(&precvframe->u.hdr.list);
list_add_tail(&precvframe->u.hdr.list, &pfree_recv_queue->queue);
if (padapter) {
if (pfree_recv_queue == &precvpriv->free_recv_queue)
precvpriv->free_recvframe_cnt++;
}
spin_unlock_irqrestore(&pfree_recv_queue->lock, irqL