/*
|
* Copyright (C) 2015 Spreadtrum Communications Inc.
|
*
|
* Authors :
|
* Keguang Zhang <keguang.zhang@spreadtrum.com>
|
* Jingxiang Li <Jingxiang.li@spreadtrum.com>
|
*
|
* This software is licensed under the terms of the GNU General Public
|
* License version 2, as published by the Free Software Foundation, and
|
* may be copied, distributed, and modified under those terms.
|
*
|
* 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.
|
*/
|
|
#include <linux/skbuff.h>
|
|
#include "sprdwl.h"
|
#include "txrx.h"
|
#include "cfg80211.h"
|
#include "cmdevt.h"
|
#include "intf_ops.h"
|
#include "work.h"
|
#if defined(UWE5621_FTR)
|
#include "wl_intf.h"
|
#endif
|
#include "rx_msg.h"
|
#include "tcp_ack.h"
|
#include "wl_core.h"
|
|
/* if err, the caller judge the skb if need free,
|
* here just free the msg buf to the freelist
|
*/
|
int sprdwl_send_data(struct sprdwl_vif *vif, struct sprdwl_msg_buf *msg,
|
struct sk_buff *skb, u8 offset)
|
{
|
int ret;
|
int delta;
|
unsigned long align_addr;
|
unsigned char *buf = NULL;
|
/*TODO temp for MARLIN2*/
|
#ifndef UWE5621_FTR
|
struct sprdwl_data_hdr *hdr;
|
#endif
|
struct sprdwl_intf *intf;
|
unsigned int plen = cpu_to_le16(skb->len);
|
|
intf = (struct sprdwl_intf *)vif->priv->hw_priv;
|
buf = skb->data;
|
/*TODO temp for MARLIN2*/
|
#ifndef UWE5621_FTR
|
skb_push(skb, sizeof(*hdr) + offset);
|
hdr = (struct sprdwl_data_hdr *)skb->data;
|
memset(hdr, 0, sizeof(*hdr));
|
hdr->common.type = SPRDWL_TYPE_DATA;
|
hdr->common.ctx_id = vif->ctx_id;
|
hdr->plen = cpu_to_le16(skb->len);
|
#else
|
if (sprdwl_intf_fill_msdu_dscr(vif, skb, SPRDWL_TYPE_DATA, offset))
|
return -EPERM;
|
#endif /* UWE5621_FTR */
|
#ifdef OTT_UWE
|
skb_push(skb, 3);
|
if ((unsigned long)skb->data & 0x3) {
|
if (!skb_pad(skb, 8)) {
|
align_addr = roundup((unsigned long)skb->data, 4);
|
delta = align_addr - (unsigned long)skb->data;
|
memmove((void *)align_addr, skb->data, skb->len);
|
skb_pull(skb, delta);
|
skb_put(skb, delta);
|
} else {
|
wl_err("addr not aligned\n");
|
return -1;
|
}
|
}
|
#endif
|
sprdwl_fill_msg(msg, skb, skb->data, skb->len);
|
|
if (sprdwl_filter_send_tcp_ack(vif->priv, msg, buf, plen))
|
return 0;
|
|
ret = sprdwl_intf_tx(vif->priv, msg);
|
if (ret)
|
wl_err("%s TX data Err: %d\n", __func__, ret);
|
|
if (intf->tdls_flow_count_enable == 1 &&
|
vif->sm_state == SPRDWL_CONNECTED)
|
count_tdls_flow(vif,
|
skb->data + offset + intf->hif_offset,
|
skb->len - offset - intf->hif_offset);
|
|
return ret;
|
}
|
|
#if defined FPGA_LOOPBACK_TEST
|
int sprdwl_send_data_fpga_test(struct sprdwl_priv *priv,
|
struct sprdwl_msg_buf *msg,
|
struct sk_buff *skb, u8 type, u8 offset)
|
{
|
int ret = 0;
|
|
sprdwl_intf_fill_msdu_dscr_test(priv, skb, type, offset);
|
msg->type = type;
|
|
sprdwl_fill_msg(msg, skb, skb->data, skb->len);
|
ret = sprdwl_intf_tx(priv, msg);
|
|
return ret;
|
}
|
#endif
|
|
int sprdwl_send_cmd(struct sprdwl_priv *priv, struct sprdwl_msg_buf *msg)
|
{
|
int ret;
|
struct sk_buff *skb;
|
|
skb = msg->skb;
|
ret = sprdwl_intf_tx(priv, msg);
|
if (ret) {
|
wl_err("%s TX cmd Err: %d\n", __func__, ret);
|
/* now cmd msg droped */
|
#if !defined(UWE5621_FTR)
|
dev_kfree_skb(skb);
|
#endif
|
}
|
|
return ret;
|
}
|
|
void sprdwl_rx_send_cmd_process(struct sprdwl_priv *priv, void *data, int len,
|
unsigned char id, unsigned char ctx_id)
|
{
|
struct sprdwl_vif *vif;
|
struct sprdwl_work *misc_work = NULL;
|
|
if (unlikely(!priv)) {
|
wl_err("%s priv not init.\n", __func__);
|
} else if (ctx_id > STAP_MODE_P2P_DEVICE) {
|
wl_err("%s [ctx_id %d]RX err\n", __func__, ctx_id);
|
} else {
|
vif = ctx_id_to_vif(priv, ctx_id);
|
if (!vif) {
|
wl_err("%s cant't get vif from ctx_id%d\n",
|
__func__, ctx_id);
|
} else {
|
misc_work = sprdwl_alloc_work(len);
|
if (!misc_work) {
|
wl_err("%s out of memory", __func__);
|
} else {
|
misc_work->vif = vif;
|
misc_work->id = id;
|
memcpy(misc_work->data, data, len);
|
sprdwl_queue_work(vif->priv, misc_work);
|
}
|
sprdwl_put_vif(vif);
|
}
|
}
|
}
|
|
void sprdwl_rx_skb_process(struct sprdwl_priv *priv, struct sk_buff *skb)
|
{
|
struct sprdwl_vif *vif = NULL;
|
struct net_device *ndev = NULL;
|
struct rx_msdu_desc *msdu_desc = NULL;
|
struct sk_buff *tx_skb = NULL;
|
struct sprdwl_intf *intf;
|
struct ethhdr *eth;
|
struct sprdwl_rx_if *rx_if;
|
|
intf = (struct sprdwl_intf *)priv->hw_priv;
|
rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx;
|
|
if (unlikely(!priv)) {
|
wl_err("%s priv not init.\n", __func__);
|
goto err;
|
}
|
|
msdu_desc = (struct rx_msdu_desc *)skb->data;
|
if (msdu_desc->ctx_id >= SPRDWL_MAC_INDEX_MAX) {
|
wl_err("%s [ctx_id %d]RX err\n", __func__, msdu_desc->ctx_id);
|
goto err;
|
}
|
|
if ((msdu_desc->amsdu_flag == 1) && (msdu_desc->snap_hdr_present == 0) &&
|
(msdu_desc->first_msdu_of_mpdu == 1)) {
|
rx_if->rx_snaphdr_flag = 1;
|
rx_if->rx_snaphdr_seqnum = msdu_desc->seq_num;
|
rx_if->rx_snaphdr_lut = msdu_desc->sta_lut_index;
|
rx_if->rx_snaphdr_tid = msdu_desc->tid;
|
wl_info("%s snaphdr attect flag %d %d %d\n", __func__, msdu_desc->seq_num,
|
msdu_desc->sta_lut_index, msdu_desc->tid);
|
if (msdu_desc->last_buff_of_mpdu == 1) {
|
rx_if->rx_snaphdr_flag = 0;
|
wl_info("%s snaphdr attect over %d last %d %d %d\n", __func__, msdu_desc->snap_hdr_present,
|
msdu_desc->last_msdu_of_mpdu, msdu_desc->last_buff_of_mpdu, msdu_desc->last_msdu_of_buff);
|
}
|
goto err;
|
}
|
|
if(rx_if->rx_snaphdr_flag == 1) {
|
if ((rx_if->rx_snaphdr_seqnum == msdu_desc->seq_num) &&
|
(rx_if->rx_snaphdr_lut == msdu_desc->sta_lut_index) &&
|
(rx_if->rx_snaphdr_tid == msdu_desc->tid)) {
|
wl_err("%s snaphdr attect %d %d %d\n", __func__, msdu_desc->seq_num,
|
msdu_desc->sta_lut_index, msdu_desc->tid);
|
if (msdu_desc->last_buff_of_mpdu == 1) {
|
rx_if->rx_snaphdr_flag = 0;
|
wl_info("%s snaphdr attect over %d %d %d %d last %d %d %d\n", __func__, msdu_desc->snap_hdr_present,
|
msdu_desc->seq_num, msdu_desc->sta_lut_index, msdu_desc->tid,
|
msdu_desc->last_msdu_of_mpdu, msdu_desc->last_buff_of_mpdu, msdu_desc->last_msdu_of_buff);
|
}
|
goto err;
|
}
|
}
|
|
vif = ctx_id_to_vif(priv, msdu_desc->ctx_id);
|
if (!vif) {
|
wl_err("%s cannot get vif, ctx_id: %d\n",
|
__func__, msdu_desc->ctx_id);
|
goto err;
|
}
|
|
/* Sanity check for bug 846454 */
|
if (vif->ndev == NULL) {
|
wl_err("%s ndev is NULL, ctx_id = %d\n",
|
__func__, msdu_desc->ctx_id);
|
BUG_ON(1);
|
}
|
|
ndev = vif->ndev;
|
skb_reserve(skb, msdu_desc->msdu_offset);
|
skb_put(skb, msdu_desc->msdu_len);
|
|
eth = (struct ethhdr *)skb->data;
|
if (eth->h_proto == htons(ETH_P_IPV6))
|
if (ether_addr_equal(skb->data, skb->data + ETH_ALEN)) {
|
wl_err("%s, drop loopback pkt, macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
__func__, skb->data[0], skb->data[1], skb->data[2],
|
skb->data[3], skb->data[4], skb->data[5]);
|
goto err;
|
}
|
|
if (intf->tdls_flow_count_enable == 1)
|
count_tdls_flow(vif,
|
skb->data + ETH_ALEN,
|
skb->len - ETH_ALEN);
|
|
/* FIXME: We would remove mode in furture, how to modify? */
|
if (((vif->mode == SPRDWL_MODE_AP) ||
|
(vif->mode == SPRDWL_MODE_P2P_GO)) && msdu_desc->uc_w2w_flag) {
|
skb->dev = ndev;
|
dev_queue_xmit(skb);
|
} else {
|
if (((vif->mode == SPRDWL_MODE_AP) ||
|
(vif->mode == SPRDWL_MODE_P2P_GO)) &&
|
msdu_desc->bc_mc_w2w_flag) {
|
struct ethhdr *eth = (struct ethhdr *)skb->data;
|
|
if (eth->h_proto != ETH_P_IP &&
|
eth->h_proto != ETH_P_IPV6) {
|
tx_skb = pskb_copy(skb, GFP_ATOMIC);
|
if (likely(tx_skb)) {
|
tx_skb->dev = ndev;
|
dev_queue_xmit(tx_skb);
|
}
|
}
|
}
|
|
/* skb->data MUST point to ETH HDR */
|
sprdwl_filter_rx_tcp_ack(priv, skb->data, msdu_desc->msdu_len);
|
/*adjust drop tcpack enable or not*/
|
#ifdef TCPACK_DROP_DYNAMIC
|
sprdwl_count_rx_tp_tcp_ack(intf, msdu_desc->msdu_len);
|
#endif
|
sprdwl_netif_rx(skb, ndev);
|
}
|
|
sprdwl_put_vif(vif);
|
|
return;
|
|
err:
|
dev_kfree_skb(skb);
|
#if defined(MORE_DEBUG)
|
intf->stats.rx_errors++;
|
intf->stats.rx_dropped++;
|
#endif
|
}
|
unsigned short sprdwl_rx_data_process(struct sprdwl_priv *priv,
|
unsigned char *msg)
|
{
|
return 0;
|
}
|