root/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c
// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
 *
 * Copyright(c) 2007 - 2012 Realtek Corporation. All rights reserved.
 *
 ******************************************************************************/

#include <drv_types.h>
#include <rtl8723b_hal.h>

static void initrecvbuf(struct recv_buf *precvbuf, struct adapter *padapter)
{
        INIT_LIST_HEAD(&precvbuf->list);
        spin_lock_init(&precvbuf->recvbuf_lock);

        precvbuf->adapter = padapter;
}

static void update_recvframe_attrib(struct adapter *padapter,
                                    union recv_frame *precvframe,
                                    struct recv_stat *prxstat)
{
        struct rx_pkt_attrib *pattrib;
        struct recv_stat report;
        struct rxreport_8723b *prxreport = (struct rxreport_8723b *)&report;

        report.rxdw0 = prxstat->rxdw0;
        report.rxdw1 = prxstat->rxdw1;
        report.rxdw2 = prxstat->rxdw2;
        report.rxdw3 = prxstat->rxdw3;
        report.rxdw4 = prxstat->rxdw4;
        report.rxdw5 = prxstat->rxdw5;

        pattrib = &precvframe->u.hdr.attrib;
        memset(pattrib, 0, sizeof(struct rx_pkt_attrib));

        /*  update rx report to recv_frame attribute */
        pattrib->pkt_rpt_type = prxreport->c2h_ind ? C2H_PACKET : NORMAL_RX;

        if (pattrib->pkt_rpt_type == NORMAL_RX) {
                /*  Normal rx packet */
                /*  update rx report to recv_frame attribute */
                pattrib->pkt_len = (u16)prxreport->pktlen;
                pattrib->drvinfo_sz = (u8)(prxreport->drvinfosize << 3);
                pattrib->physt = (u8)prxreport->physt;

                pattrib->crc_err = (u8)prxreport->crc32;
                pattrib->icv_err = (u8)prxreport->icverr;

                pattrib->bdecrypted = (u8)(prxreport->swdec ? 0 : 1);
                pattrib->encrypt = (u8)prxreport->security;

                pattrib->qos = (u8)prxreport->qos;
                pattrib->priority = (u8)prxreport->tid;

                pattrib->amsdu = (u8)prxreport->amsdu;

                pattrib->seq_num = (u16)prxreport->seq;
                pattrib->frag_num = (u8)prxreport->frag;
                pattrib->mfrag = (u8)prxreport->mf;
                pattrib->mdata = (u8)prxreport->md;

                pattrib->data_rate = (u8)prxreport->rx_rate;
        } else {
                pattrib->pkt_len = (u16)prxreport->pktlen;
        }
}

/*
 * Notice:
 *Before calling this function,
 *precvframe->u.hdr.rx_data should be ready!
 */
static void update_recvframe_phyinfo(union recv_frame *precvframe,
                                     struct phy_stat *pphy_status)
{
        struct adapter *padapter = precvframe->u.hdr.adapter;
        struct rx_pkt_attrib *pattrib = &precvframe->u.hdr.attrib;
        struct hal_com_data *p_hal_data = GET_HAL_DATA(padapter);
        struct odm_phy_info *p_phy_info =
                (struct odm_phy_info *)(&pattrib->phy_info);

        u8 *wlanhdr = precvframe->u.hdr.rx_data;
        u8 *my_bssid;
        u8 *rx_bssid;
        u8 *rx_ra;
        u8 *my_hwaddr;
        u8 *sa = NULL;

        struct odm_packet_info pkt_info = {
                .data_rate   = 0x00,
                .station_id  = 0x00,
                .bssid_match = false,
                .to_self     = false,
                .is_beacon   = false,
        };

        /* unsigned long                irqL; */
        struct sta_priv *pstapriv;
        struct sta_info *psta;

        my_bssid = get_bssid(&padapter->mlmepriv);
        rx_bssid = get_hdr_bssid(wlanhdr);
        pkt_info.bssid_match = ((!IsFrameTypeCtrl(wlanhdr)) &&
                                !pattrib->icv_err && !pattrib->crc_err &&
                                ether_addr_equal(rx_bssid, my_bssid));

        rx_ra = rtl8723bs_get_ra(wlanhdr);
        my_hwaddr = myid(&padapter->eeprompriv);
        pkt_info.to_self = pkt_info.bssid_match &&
                ether_addr_equal(rx_ra, my_hwaddr);

        pkt_info.is_beacon = pkt_info.bssid_match &&
                (GetFrameSubType(wlanhdr) == WIFI_BEACON);

        sa = get_ta(wlanhdr);

        pkt_info.station_id = 0xFF;

        pstapriv = &padapter->stapriv;
        psta = rtw_get_stainfo(pstapriv, sa);
        if (psta)
                pkt_info.station_id = psta->mac_id;

        pkt_info.data_rate = pattrib->data_rate;

        /* rtl8723b_query_rx_phy_status(precvframe, pphy_status); */
        /* spin_lock_bh(&p_hal_data->odm_stainfo_lock); */
        odm_phy_status_query(&p_hal_data->odmpriv, p_phy_info,
                           (u8 *)pphy_status, &(pkt_info));
        if (psta)
                psta->rssi = pattrib->phy_info.RecvSignalPower;
        /* spin_unlock_bh(&p_hal_data->odm_stainfo_lock); */
        precvframe->u.hdr.psta = NULL;
        if (
                pkt_info.bssid_match &&
                (check_fwstate(&padapter->mlmepriv, WIFI_AP_STATE))
        ) {
                if (psta) {
                        precvframe->u.hdr.psta = psta;
                        rtl8723b_process_phy_info(padapter, precvframe);
                }
        } else if (pkt_info.to_self || pkt_info.is_beacon) {
                u32 adhoc_state = WIFI_ADHOC_STATE | WIFI_ADHOC_MASTER_STATE;

                if (check_fwstate(&padapter->mlmepriv, adhoc_state))
                        if (psta)
                                precvframe->u.hdr.psta = psta;
                rtl8723b_process_phy_info(padapter, precvframe);
        }
}

static void rtl8723bs_c2h_packet_handler(struct adapter *padapter,
                                         u8 *pbuf, u16 length)
{
        u8 *tmp = NULL;
        u8 res = false;

        if (length == 0)
                return;

        tmp = kmemdup(pbuf, length, GFP_ATOMIC);
        if (!tmp)
                return;

        res = rtw_c2h_packet_wk_cmd(padapter, tmp, length);

        if (!res)
                kfree(tmp);
}

static inline union recv_frame *try_alloc_recvframe(struct recv_priv *precvpriv,
                                                    struct recv_buf *precvbuf)
{
        union recv_frame *precvframe;

        precvframe = rtw_alloc_recvframe(&precvpriv->free_recv_queue);
        if (!precvframe) {
                rtw_enqueue_recvbuf_to_head(precvbuf,
                                            &precvpriv->recv_buf_pending_queue);

                /*  The case of can't allocate recvframe should be temporary, */
                /*  schedule again and hope recvframe is available next time. */
                tasklet_schedule(&precvpriv->recv_tasklet);
        }

        return precvframe;
}

static inline bool rx_crc_err(struct recv_priv *precvpriv,
                              struct hal_com_data *p_hal_data,
                              struct rx_pkt_attrib *pattrib,
                              union recv_frame *precvframe)
{
        /*  fix Hardware RX data error, drop whole recv_buffer */
        if ((!(p_hal_data->ReceiveConfig & RCR_ACRC32)) && pattrib->crc_err) {
                rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
                return true;
        }

        return false;
}

static inline bool pkt_exceeds_tail(struct recv_priv *precvpriv,
                                    u8 *end, u8 *tail,
                                    union recv_frame *precvframe)
{
        if (end > tail) {
                rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
                return true;
        }

        return false;
}

static void rtl8723bs_recv_tasklet(struct tasklet_struct *t)
{
        struct adapter *padapter = from_tasklet(padapter, t,
                                                recvpriv.recv_tasklet);
        struct hal_com_data *p_hal_data;
        struct recv_priv *precvpriv;
        struct recv_buf *precvbuf;
        union recv_frame *precvframe;
        struct rx_pkt_attrib *pattrib;
        struct __queue *recv_buf_queue;
        u8 *ptr;
        u32 pkt_offset, skb_len, alloc_sz;
        struct sk_buff *pkt_copy = NULL;
        u8 shift_sz = 0, rx_report_sz = 0;

        p_hal_data = GET_HAL_DATA(padapter);
        precvpriv = &padapter->recvpriv;
        recv_buf_queue = &precvpriv->recv_buf_pending_queue;

        do {
                precvbuf = rtw_dequeue_recvbuf(recv_buf_queue);
                if (!precvbuf)
                        break;

                ptr = precvbuf->pdata;

                while (ptr < precvbuf->ptail) {
                        precvframe = try_alloc_recvframe(precvpriv, precvbuf);
                        if (!precvframe)
                                return;

                        /* rx desc parsing */
                        update_recvframe_attrib(padapter, precvframe,
                                                (struct recv_stat *)ptr);

                        pattrib = &precvframe->u.hdr.attrib;

                        if (rx_crc_err(precvpriv, p_hal_data,
                                       pattrib, precvframe))
                                break;

                        rx_report_sz = RXDESC_SIZE + pattrib->drvinfo_sz;
                        pkt_offset = rx_report_sz +
                                pattrib->shift_sz +
                                pattrib->pkt_len;

                        if (pkt_exceeds_tail(precvpriv, ptr + pkt_offset,
                                             precvbuf->ptail, precvframe))
                                break;

                        if ((pattrib->crc_err) || (pattrib->icv_err)) {
                                rtw_free_recvframe(precvframe,
                                                   &precvpriv->free_recv_queue);
                        } else {
                                /*      Modified by Albert 20101213 */
                                /*      For 8 bytes IP header alignment. */
                                if (pattrib->qos)       /*      Qos data, wireless lan header length is 26 */
                                        shift_sz = 6;
                                else
                                        shift_sz = 0;

                                skb_len = pattrib->pkt_len;

                                /*  for first fragment packet, driver need allocate 1536+drvinfo_sz+RXDESC_SIZE to defrag packet. */
                                /*  modify alloc_sz for recvive crc error packet by thomas 2011-06-02 */
                                if ((pattrib->mfrag == 1) && (pattrib->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_copy = __dev_alloc_skb(alloc_sz, GFP_ATOMIC);
                                if (!pkt_copy) {
                                        rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
                                        break;
                                }

                                pkt_copy->dev = padapter->pnetdev;
                                precvframe->u.hdr.pkt = pkt_copy;
                                skb_reserve(pkt_copy, 8 - ((SIZE_PTR)(pkt_copy->data) & 7));/* force pkt_copy->data at 8-byte alignment address */
                                skb_reserve(pkt_copy, shift_sz);/* force ip_hdr at 8-byte alignment address according to shift_sz. */
                                memcpy(pkt_copy->data, (ptr + rx_report_sz + pattrib->shift_sz), skb_len);
                                precvframe->u.hdr.rx_head = pkt_copy->head;
                                precvframe->u.hdr.rx_data = precvframe->u.hdr.rx_tail = pkt_copy->data;
                                precvframe->u.hdr.rx_end = skb_end_pointer(pkt_copy);

                                recvframe_put(precvframe, skb_len);
                                /* recvframe_pull(precvframe, drvinfo_sz + RXDESC_SIZE); */

                                if (p_hal_data->ReceiveConfig & RCR_APPFCS)
                                        recvframe_pull_tail(precvframe, IEEE80211_FCS_LEN);

                                /*  move to drv info position */
                                ptr += RXDESC_SIZE;

                                /*  update drv info */
                                if (p_hal_data->ReceiveConfig & RCR_APP_BA_SSN) {
                                        /* rtl8723s_update_bassn(padapter, pdrvinfo); */
                                        ptr += 4;
                                }

                                if (pattrib->pkt_rpt_type == NORMAL_RX) { /* Normal rx packet */
                                        if (pattrib->physt)
                                                update_recvframe_phyinfo(precvframe, (struct phy_stat *)ptr);

                                        rtw_recv_entry(precvframe);
                                } else if (pattrib->pkt_rpt_type == C2H_PACKET) {
                                        struct c2h_evt_hdr_t    C2hEvent;

                                        u16 len_c2h = pattrib->pkt_len;
                                        u8 *pbuf_c2h = precvframe->u.hdr.rx_data;
                                        u8 *pdata_c2h;

                                        C2hEvent.CmdID = pbuf_c2h[0];
                                        C2hEvent.CmdSeq = pbuf_c2h[1];
                                        C2hEvent.CmdLen = (len_c2h-2);
                                        pdata_c2h = pbuf_c2h+2;

                                        if (C2hEvent.CmdID == C2H_CCX_TX_RPT)
                                                CCX_FwC2HTxRpt_8723b(padapter, pdata_c2h, C2hEvent.CmdLen);
                                        else
                                                rtl8723bs_c2h_packet_handler(padapter, precvframe->u.hdr.rx_data, pattrib->pkt_len);

                                        rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
                                }
                        }

                        pkt_offset = round_up(pkt_offset, 8);
                        precvbuf->pdata += pkt_offset;
                        ptr = precvbuf->pdata;
                        precvframe = NULL;
                        pkt_copy = NULL;
                }

                rtw_enqueue_recvbuf(precvbuf, &precvpriv->free_recv_buf_queue);
        } while (1);
}

/*
 * Initialize recv private variable for hardware dependent
 * 1. recv buf
 * 2. recv tasklet
 *
 */
s32 rtl8723bs_init_recv_priv(struct adapter *padapter)
{
        s32 res;
        u32 i, n;
        struct recv_priv *precvpriv;
        struct recv_buf *precvbuf;

        res = _SUCCESS;
        precvpriv = &padapter->recvpriv;

        /* 3 1. init recv buffer */
        INIT_LIST_HEAD(&precvpriv->free_recv_buf_queue.queue);
        spin_lock_init(&precvpriv->free_recv_buf_queue.lock);
        INIT_LIST_HEAD(&precvpriv->recv_buf_pending_queue.queue);
        spin_lock_init(&precvpriv->recv_buf_pending_queue.lock);

        n = NR_RECVBUFF * sizeof(struct recv_buf) + 4;
        precvpriv->pallocated_recv_buf = kzalloc(n, GFP_KERNEL);
        if (!precvpriv->pallocated_recv_buf) {
                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++) {
                initrecvbuf(precvbuf, padapter);

                if (!precvbuf->pskb) {
                        SIZE_PTR tmpaddr = 0;
                        SIZE_PTR alignment = 0;

                        precvbuf->pskb = __dev_alloc_skb(MAX_RECVBUF_SZ + RECVBUFF_ALIGN_SZ, GFP_ATOMIC);
                        if (precvbuf->pskb) {
                                precvbuf->pskb->dev = padapter->pnetdev;

                                tmpaddr = (SIZE_PTR)precvbuf->pskb->data;
                                alignment = tmpaddr & (RECVBUFF_ALIGN_SZ-1);
                                skb_reserve(precvbuf->pskb, (RECVBUFF_ALIGN_SZ - alignment));
                        }
                }

                list_add_tail(&precvbuf->list, &precvpriv->free_recv_buf_queue.queue);

                precvbuf++;
        }
        precvpriv->free_recv_buf_queue_cnt = i;

        if (res == _FAIL)
                goto initbuferror;

        /* 3 2. init tasklet */
        tasklet_setup(&precvpriv->recv_tasklet, rtl8723bs_recv_tasklet);

        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++) {
                        list_del_init(&precvbuf->list);
                        if (precvbuf->pskb)
                                dev_kfree_skb_any(precvbuf->pskb);
                        precvbuf++;
                }
                precvpriv->precv_buf = NULL;
        }

        kfree(precvpriv->pallocated_recv_buf);
        precvpriv->pallocated_recv_buf = NULL;

exit:
        return res;
}

/*
 * Free recv private variable of hardware dependent
 * 1. recv buf
 * 2. recv tasklet
 *
 */
void rtl8723bs_free_recv_priv(struct adapter *padapter)
{
        u32 i;
        struct recv_priv *precvpriv;
        struct recv_buf *precvbuf;

        precvpriv = &padapter->recvpriv;

        /* 3 1. kill tasklet */
        tasklet_kill(&precvpriv->recv_tasklet);

        /* 3 2. free all recv buffers */
        precvbuf = (struct recv_buf *)precvpriv->precv_buf;
        if (precvbuf) {
                precvpriv->free_recv_buf_queue_cnt = 0;
                for (i = 0; i < NR_RECVBUFF; i++) {
                        list_del_init(&precvbuf->list);
                        if (precvbuf->pskb)
                                dev_kfree_skb_any(precvbuf->pskb);
                        precvbuf++;
                }
                precvpriv->precv_buf = NULL;
        }

        kfree(precvpriv->pallocated_recv_buf);
        precvpriv->pallocated_recv_buf = NULL;
}