/*
|
* Copyright (C) 2016 Spreadtrum Communications Inc.
|
*
|
* Authors :
|
* star.liu <star.liu@spreadtrum.com>
|
* yifei.li <yifei.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/ip.h>
|
#include "sprdwl.h"
|
#include "wl_intf.h"
|
#include "intf_ops.h"
|
#include "mm.h"
|
#include "tx_msg.h"
|
#include "rx_msg.h"
|
#include "work.h"
|
#include "tcp_ack.h"
|
|
#define INIT_INTF(num, type, out, interval, bsize, psize, max,\
|
threshold, time, pop, push, complete, suspend) \
|
{ .channel = num, .hif_type = type, .inout = out, .intr_interval = interval,\
|
.buf_size = bsize, .pool_size = psize, .once_max_trans = max,\
|
.rx_threshold = threshold, .timeout = time, .pop_link = pop,\
|
.push_link = push, .tx_complete = complete, .power_notify = suspend }
|
|
struct sprdwl_intf_ops g_intf_ops;
|
|
static inline struct sprdwl_intf *get_intf(void)
|
{
|
return (struct sprdwl_intf *)g_intf_ops.intf;
|
}
|
|
#define INTF_IS_PCIE \
|
(get_intf()->priv->hw_type == SPRDWL_HW_PCIE)
|
|
void sprdwl_hex_dump(unsigned char *name,
|
unsigned char *data, unsigned short len)
|
{
|
int i, p = 0, ret;
|
unsigned char buf[255] = {0};
|
|
if ((NULL == data) || (0 == len) || (NULL == name))
|
return;
|
|
sprintf(buf, "sprdwl %s hex dump(len = %d)", name, len);
|
wl_info("%s\n", buf);
|
|
if (len > 1024)
|
len = 1024;
|
memset(buf, 0x00, 255);
|
for (i = 0; i < len ; i++) {
|
ret = sprintf((buf + p), "%02x ", *(data + i));
|
if ((i != 0) && ((i + 1)%16 == 0)) {
|
wl_info("%s\n", buf);
|
p = 0;
|
memset(buf, 0x00, 255);
|
} else {
|
p = p + ret;
|
}
|
}
|
if (p != 0)
|
wl_info("%s\n", buf);
|
}
|
|
enum sprdwl_hw_type get_hwintf_type(void)
|
{
|
int hif = 0;
|
enum sprdwl_hw_type hw_type;
|
|
hif = sprdwcn_bus_get_hif_type();
|
switch (hif) {
|
case HW_TYPE_SDIO:
|
hw_type = SPRDWL_HW_SDIO;
|
break;
|
case HW_TYPE_PCIE:
|
hw_type = SPRDWL_HW_PCIE;
|
break;
|
case HW_TYPE_SIPC:
|
hw_type = SPRDWL_HW_SIPC;
|
break;
|
case HW_TYPE_USB:
|
hw_type = SPRDWL_HW_USB;
|
break;
|
default:
|
hw_type = SPRDWL_HW_SDIO;
|
break;
|
}
|
return hw_type;
|
}
|
|
#if defined(MORE_DEBUG)
|
void sprdwl_dump_stats(struct sprdwl_intf *intf)
|
{
|
wl_err("++print txrx statistics++\n");
|
wl_err("tx packets: %lu, tx bytes: %lu\n", intf->stats.tx_packets,
|
intf->stats.tx_bytes);
|
wl_err("tx filter num: %lu\n", intf->stats.tx_filter_num);
|
wl_err("tx errors: %lu, tx dropped: %lu\n", intf->stats.tx_errors,
|
intf->stats.tx_dropped);
|
wl_err("tx avg time: %lu\n", intf->stats.tx_avg_time);
|
wl_err("tx realloc: %lu\n", intf->stats.tx_realloc);
|
wl_err("tx arp num: %lu\n", intf->stats.tx_arp_num);
|
wl_err("rx packets: %lu, rx bytes: %lu\n", intf->stats.rx_packets,
|
intf->stats.rx_bytes);
|
wl_err("rx errors: %lu, rx dropped: %lu\n", intf->stats.rx_errors,
|
intf->stats.rx_dropped);
|
wl_err("rx multicast: %lu, tx multicast: %lu\n",
|
intf->stats.rx_multicast, intf->stats.tx_multicast);
|
wl_err("--print txrx statistics--\n");
|
}
|
|
void sprdwl_clear_stats(struct sprdwl_intf *intf)
|
{
|
memset(&intf->stats, 0x0, sizeof(struct txrx_stats));
|
}
|
|
/*calculate packets average sent time from received
|
*from network stack to freed by HIF every STATS_COUNT packets
|
*/
|
void sprdwl_get_tx_avg_time(struct sprdwl_intf *intf,
|
unsigned long tx_start_time)
|
{
|
struct timespec tx_end;
|
|
getnstimeofday(&tx_end);
|
intf->stats.tx_cost_time +=
|
timespec_to_ns(&tx_end) - tx_start_time;
|
if (intf->stats.gap_num >= STATS_COUNT) {
|
intf->stats.tx_avg_time =
|
intf->stats.tx_cost_time / intf->stats.gap_num;
|
sprdwl_dump_stats(intf);
|
intf->stats.gap_num = 0;
|
intf->stats.tx_cost_time = 0;
|
wl_info("%s:%d packets avg cost time: %lu\n",
|
__func__, __LINE__, intf->stats.tx_avg_time);
|
}
|
}
|
#endif
|
|
void set_coex_bt_on_off(u8 action)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
|
intf->coex_bt_on = action;
|
}
|
|
unsigned long mbufalloc;
|
unsigned long mbufpop;
|
int if_tx_one(struct sprdwl_intf *intf, unsigned char *data,
|
int len, int chn)
|
{
|
int ret;
|
struct mbuf_t *head = NULL, *tail = NULL, *mbuf = NULL;
|
int num = 1;
|
|
ret = sprdwcn_bus_list_alloc(chn, &head, &tail, &num);
|
if (ret || head == NULL || tail == NULL) {
|
wl_err("%s:%d sprdwcn_bus_list_alloc fail\n",
|
__func__, __LINE__);
|
return -1;
|
}
|
mbufalloc += num;
|
mbuf = head;
|
mbuf->buf = data;
|
|
mbuf->len = len;
|
mbuf->next = NULL;
|
if (sprdwl_debug_level >= L_DBG)
|
sprdwl_hex_dump("tx to cp2 cmd data dump", data + 4, len);
|
if (intf->priv->hw_type == SPRDWL_HW_PCIE) {
|
mbuf->phy = mm_virt_to_phys(&intf->pdev->dev, mbuf->buf,
|
mbuf->len, DMA_TO_DEVICE);
|
}
|
|
ret = sprdwcn_bus_push_list(chn, head, tail, num);
|
if (ret) {
|
mbuf = head;
|
if (intf->priv->hw_type == SPRDWL_HW_PCIE) {
|
mm_phys_to_virt(&intf->pdev->dev, mbuf->phy, mbuf->len,
|
DMA_TO_DEVICE, false);
|
mbuf->phy = 0;
|
}
|
kfree(mbuf->buf);
|
mbuf->buf = NULL;
|
|
sprdwcn_bus_list_free(chn, head, tail, num);
|
mbufalloc -= num;
|
}
|
|
return ret;
|
}
|
|
inline int if_tx_cmd(struct sprdwl_intf *intf, unsigned char *data, int len)
|
{
|
return if_tx_one(intf, data, len, intf->tx_cmd_port);
|
}
|
|
inline int if_tx_addr_trans(struct sprdwl_intf *intf,
|
unsigned char *data, int len)
|
{
|
/* FIXME: Which port is used to send ADDR TRNAS*/
|
return if_tx_one(intf, data, len, intf->tx_data_port);
|
}
|
|
static inline struct pcie_addr_buffer
|
*sprdwl_alloc_pcie_addr_buf(int tx_count)
|
{
|
struct pcie_addr_buffer *addr_buffer;
|
#define ADDR_OFFSET 5
|
|
addr_buffer =
|
kzalloc(sizeof(struct pcie_addr_buffer) +
|
tx_count * SPRDWL_PHYS_LEN, GFP_KERNEL);
|
if (addr_buffer == NULL) {
|
wl_err("%s:%d alloc pcie addr buf fail\n", __func__, __LINE__);
|
return NULL;
|
}
|
addr_buffer->common.type = SPRDWL_TYPE_DATA_PCIE_ADDR;
|
addr_buffer->common.direction_ind = 0;
|
addr_buffer->number = tx_count;
|
addr_buffer->offset = ADDR_OFFSET;
|
addr_buffer->buffer_ctrl.buffer_inuse = 1;
|
addr_buffer->buffer_ctrl.buffer_type = 1;
|
|
return addr_buffer;
|
}
|
|
static inline struct pcie_addr_buffer
|
*sprdwl_set_pcie_addr_to_mbuf(struct sprdwl_tx_msg *tx_msg, struct mbuf_t *mbuf,
|
int tx_count)
|
{
|
#define ADDR_OFFSET 5
|
struct pcie_addr_buffer *addr_buffer;
|
struct sprdwl_intf *intf = tx_msg->intf;
|
|
addr_buffer = sprdwl_alloc_pcie_addr_buf(tx_count);
|
if (addr_buffer == NULL)
|
return NULL;
|
mbuf->len = ADDR_OFFSET + tx_count * SPRDWL_PHYS_LEN;
|
mbuf->buf = (unsigned char *)addr_buffer;
|
mbuf->phy = mm_virt_to_phys(&intf->pdev->dev, mbuf->buf,
|
mbuf->len, DMA_TO_DEVICE);
|
|
return addr_buffer;
|
}
|
|
void sprdwl_add_tx_list_head(struct list_head *tx_fail_list,
|
struct list_head *tx_list,
|
int ac_index,
|
int tx_count)
|
{
|
struct sprdwl_msg_buf *msg_buf = NULL;
|
struct list_head *xmit_free_list;
|
struct list_head *head, *tail;
|
spinlock_t *lock;
|
spinlock_t *free_lock;
|
|
if (tx_fail_list == NULL)
|
return;
|
msg_buf = list_first_entry(tx_fail_list, struct sprdwl_msg_buf, list);
|
xmit_free_list = &msg_buf->xmit_msg_list->to_free_list;
|
free_lock = &msg_buf->xmit_msg_list->free_lock;
|
if (msg_buf->msg_type != SPRDWL_TYPE_DATA) {
|
lock = &msg_buf->msglist->busylock;
|
} else {
|
if (SPRDWL_AC_MAX != ac_index)
|
lock = &msg_buf->data_list->p_lock;
|
else
|
lock = &msg_buf->xmit_msg_list->send_lock;
|
}
|
spin_lock_bh(free_lock);
|
head = tx_fail_list->next;
|
tail = tx_fail_list->prev;
|
head->prev->next = tail->next;
|
tail->next->prev = head->prev;
|
head->prev = tx_fail_list;
|
tail->next = tx_fail_list;
|
spin_unlock_bh(free_lock);
|
|
spin_lock_bh(lock);
|
list_splice(tx_fail_list, tx_list);
|
spin_unlock_bh(lock);
|
INIT_LIST_HEAD(tx_fail_list);
|
}
|
|
static inline void
|
sprdwl_add_to_free_list(struct sprdwl_tx_msg *tx_msg,
|
struct list_head *tx_list_head,
|
int tx_count)
|
{
|
spin_lock_bh(&tx_msg->xmit_msg_list.free_lock);
|
list_splice_tail(tx_list_head, &tx_msg->xmit_msg_list.to_free_list);
|
spin_unlock_bh(&tx_msg->xmit_msg_list.free_lock);
|
}
|
|
/*cut data list from tx data list*/
|
static inline void
|
sprdwl_list_cut_position(struct list_head *tx_list_head,
|
struct list_head *tx_list,
|
struct list_head *tail_entry, int ac_index)
|
{
|
spinlock_t *lock;
|
struct sprdwl_msg_buf *msg_buf = NULL;
|
|
if (tail_entry == NULL)
|
return;
|
msg_buf = list_first_entry(tx_list, struct sprdwl_msg_buf, list);
|
if (msg_buf->msg_type != SPRDWL_TYPE_DATA) {
|
lock = &msg_buf->msglist->busylock;
|
} else {
|
if (SPRDWL_AC_MAX != ac_index)
|
lock = &msg_buf->data_list->p_lock;
|
else
|
lock = &msg_buf->xmit_msg_list->send_lock;
|
}
|
spin_lock_bh(lock);
|
list_cut_position(tx_list_head, tx_list, tail_entry);
|
spin_unlock_bh(lock);
|
}
|
|
int sprdwl_add_topop_list(int chn, struct mbuf_t *head,
|
struct mbuf_t *tail, int num)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
struct sprdwl_work *misc_work;
|
struct sprdwl_pop_work pop_work;
|
|
pop_work.chn = chn;
|
pop_work.head = (void *)head;
|
pop_work.tail = (void *)tail;
|
pop_work.num = num;
|
|
misc_work = sprdwl_alloc_work(sizeof(struct sprdwl_pop_work));
|
if (!misc_work) {
|
wl_err("%s out of memory\n", __func__);
|
return -1;
|
}
|
misc_work->vif = NULL;
|
misc_work->id = SPRDWL_POP_MBUF;
|
memcpy(misc_work->data, &pop_work, sizeof(struct sprdwl_pop_work));
|
|
sprdwl_queue_work(intf->priv, misc_work);
|
return 0;
|
}
|
|
void sprdwl_count_tx_tp(struct sprdwl_tx_msg *tx_msg, int num)
|
{
|
long long timeus = 0;
|
struct sprdwl_intf *intf = get_intf();
|
|
tx_msg->tx_data_num += num;
|
if (tx_msg->tx_data_num == num) {
|
tx_msg->txtimebegin = ktime_get();
|
return;
|
}
|
|
tx_msg->txtimeend = ktime_get();
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
timeus = div_u64(tx_msg->txtimeend.tv64 - tx_msg->txtimebegin.tv64, NSEC_PER_USEC);
|
#else
|
timeus = div_u64(tx_msg->txtimeend - tx_msg->txtimebegin, NSEC_PER_USEC);
|
#endif
|
|
if(!timeus) {
|
return;
|
}
|
|
if (div_u64((tx_msg->tx_data_num * 1000), timeus) >= intf->txnum_level &&
|
tx_msg->tx_data_num >= 1000) {
|
tx_msg->tx_data_num = 0;
|
#ifdef CPUFREQ_UPDATE_SUPPORT
|
sprdwl_boost();
|
#endif /* CPUFREQ_UPDATE_SUPPORT */
|
} else if (timeus >= USEC_PER_SEC) {
|
tx_msg->tx_data_num = 0;
|
}
|
}
|
|
unsigned long tx_packets;
|
int sprdwl_intf_tx_list(struct sprdwl_intf *dev,
|
struct list_head *tx_list,
|
struct list_head *tx_list_head,
|
int tx_count,
|
int ac_index,
|
u8 coex_bt_on)
|
{
|
#define PCIE_TX_NUM 96
|
int ret, i = 0, j = PCIE_TX_NUM, pcie_count = 0, cnt = 0, num = 0;
|
struct sprdwl_msg_buf *msg_pos, *msg_temp;
|
struct pcie_addr_buffer *addr_buffer = NULL;
|
struct sprdwl_tx_msg *tx_msg;
|
struct mbuf_t *head = NULL, *tail = NULL, *mbuf_pos;
|
unsigned long pcie_addr = 0;
|
/*struct sprdwl_data_hdr *hdr; *//*temp for test*/
|
struct list_head *pos, *tx_list_tail, *n_list;
|
unsigned long *msg_ptr;
|
unsigned char *data_ptr;
|
struct tx_msdu_dscr *dscr;
|
#if defined(MORE_DEBUG)
|
unsigned long tx_bytes = 0;
|
#endif
|
int tx_count_saved = tx_count;
|
int list_num;
|
|
if(dev->cp_asserted ==1)
|
return 0;
|
|
wl_debug("%s:%d tx_count is %d\n", __func__, __LINE__, tx_count);
|
list_num = get_list_num(tx_list);
|
if (list_num < tx_count) {
|
wl_err("%s, %d, error!, tx_count:%d, list_num:%d\n",
|
__func__, __LINE__, tx_count, list_num);
|
WARN_ON(1);
|
}
|
tx_msg = (struct sprdwl_tx_msg *)dev->sprdwl_tx;
|
sprdwl_count_tx_tp(tx_msg, tx_count);
|
if (dev->priv->hw_type == SPRDWL_HW_PCIE) {
|
if (tx_count <= PCIE_TX_NUM) {
|
pcie_count = 1;
|
} else {
|
cnt = tx_count;
|
while (cnt > PCIE_TX_NUM) {
|
++num;
|
cnt -= PCIE_TX_NUM;
|
}
|
pcie_count = num + 1;
|
}
|
ret = sprdwcn_bus_list_alloc(dev->tx_data_port, &head,
|
&tail, &pcie_count);
|
} else {
|
ret = sprdwcn_bus_list_alloc(dev->tx_data_port, &head,
|
&tail, &tx_count);
|
}
|
if (ret != 0 || head == NULL || tail == NULL) {
|
wl_err("%s, %d, mbuf alloc fail\n",
|
__func__, __LINE__);
|
sprdwcn_bus_list_free(dev->tx_data_port, head, tail, tx_count);
|
return -ENOMEM;
|
}
|
if (tx_count_saved != tx_count) {
|
wl_err("%s, %d error!mbuf not enough%d\n",
|
__func__, __LINE__, (tx_count_saved - tx_count));
|
tx_msg->mbuf_short += (tx_count_saved - tx_count);
|
sprdwcn_bus_list_free(dev->tx_data_port, head, tail, tx_count);
|
return -ENOMEM;
|
}
|
mbufalloc += tx_count;
|
|
mbuf_pos = head;
|
if (dev->priv->hw_type == SPRDWL_HW_PCIE) {
|
if (pcie_count > 1) {
|
addr_buffer =
|
sprdwl_set_pcie_addr_to_mbuf(tx_msg,
|
mbuf_pos, PCIE_TX_NUM);
|
} else {
|
addr_buffer =
|
sprdwl_set_pcie_addr_to_mbuf(tx_msg,
|
mbuf_pos, tx_count);
|
}
|
if (addr_buffer == NULL) {
|
wl_err("%s:%d alloc pcie addr buf fail\n",
|
__func__, __LINE__);
|
return -1;
|
}
|
}
|
list_for_each_safe(pos, n_list, tx_list) {
|
msg_pos = list_entry(pos, struct sprdwl_msg_buf, list);
|
sprdwl_move_tcpack_msg(dev->priv, msg_pos);
|
data_ptr = (unsigned char *)(msg_pos->tran_data) -
|
dev->hif_offset;
|
#ifdef OTT_UWE
|
dscr = (struct tx_msdu_dscr *)(msg_pos->tran_data + FOUR_BYTES_ALIGN_OFFSET);
|
#else
|
dscr = (struct tx_msdu_dscr *)(msg_pos->tran_data);
|
#endif
|
dscr->color_bit = sprdwl_fc_set_clor_bit(tx_msg, i + 1);
|
/*TODO*/
|
if (sprdwl_debug_level >= L_DBG) {
|
int print_len = msg_pos->len;
|
|
if (print_len > 200)
|
print_len = 200;
|
sprdwl_hex_dump("tx to cp2 data",
|
(unsigned char *)(msg_pos->tran_data),
|
print_len);
|
}
|
#if defined(MORE_DEBUG)
|
tx_bytes += msg_pos->skb->len;
|
#endif
|
msg_ptr =
|
(unsigned long *)(data_ptr - sizeof(unsigned long *));
|
/*store msg_buf ptr to skb header room
|
*for call back func free
|
*/
|
*msg_ptr = (unsigned long)msg_pos;
|
if (dev->priv->hw_type == SPRDWL_HW_PCIE) {
|
if (pcie_count > 1 && num > 0 && i >= j) {
|
if (--num == 0) {
|
if (cnt > 0)
|
addr_buffer =
|
sprdwl_set_pcie_addr_to_mbuf(
|
tx_msg, mbuf_pos, cnt);
|
} else {
|
/*if data num greater than PCIE_TX_NUM,
|
*alloc another pcie addr buf
|
*/
|
j += PCIE_TX_NUM;
|
addr_buffer =
|
sprdwl_set_pcie_addr_to_mbuf(
|
tx_msg, mbuf_pos, PCIE_TX_NUM);
|
}
|
if (addr_buffer == NULL) {
|
wl_err("%s:%d alloc pcie addr buf fail\n",
|
__func__, __LINE__);
|
return -1;
|
}
|
}
|
|
pcie_addr =
|
mm_virt_to_phys(&dev->pdev->dev,
|
msg_pos->tran_data,
|
msg_pos->len, DMA_TO_DEVICE);
|
memcpy(&addr_buffer->pcie_addr[i],
|
&pcie_addr, SPRDWL_PHYS_LEN);
|
} else {
|
mbuf_pos->buf = data_ptr;
|
mbuf_pos->len = msg_pos->len;
|
/*TODO, to check msgbuf before list push*/
|
msg_temp = GET_MSG_BUF(mbuf_pos);
|
if (!virt_addr_valid(msg_temp) ||
|
!virt_addr_valid(msg_temp->skb))
|
BUG_ON(1);
|
}
|
mbuf_pos = mbuf_pos->next;
|
if (++i == tx_count)
|
break;
|
}
|
|
tx_list_tail = pos;
|
sprdwl_list_cut_position(tx_list_head, tx_list, tx_list_tail,
|
ac_index);
|
sprdwl_add_to_free_list(tx_msg, tx_list_head, tx_count);
|
|
if (dev->priv->hw_type == SPRDWL_HW_PCIE) {
|
/*ret = sprdwcn_bus_push_list(dev->tx_data_port, head, tail,*/
|
/*pcie_count);*/
|
/*edma sync function*/
|
ret = sprdwcn_bus_push_link_wait_complete(dev->tx_data_port, head,
|
tail, pcie_count, 50000);
|
if (ret != 0) {
|
for (mbuf_pos = head; mbuf_pos != NULL;
|
mbuf_pos = mbuf_pos->next) {
|
kfree(mbuf_pos->buf);
|
mbuf_pos->buf = NULL;
|
if (--pcie_count == 0)
|
break;
|
}
|
sprdwcn_bus_list_free(dev->tx_data_port, head, tail,
|
pcie_count);
|
/*add data list to tx list header if tx fail*/
|
sprdwl_add_tx_list_head(tx_list_head,
|
tx_list, ac_index, tx_count);
|
wl_err("%s:%d Tx pcie addr buf fail\n",
|
__func__, __LINE__);
|
} else {
|
#if defined(MORE_DEBUG)
|
UPDATE_TX_PACKETS(dev, tx_count, tx_bytes);
|
#endif
|
INIT_LIST_HEAD(tx_list_head);
|
}
|
return ret;
|
}
|
|
/*BT is on: call sdiohal_txthread to send data.*/
|
if (dev->priv->hw_type == SPRDWL_HW_SDIO) {
|
if (coex_bt_on)
|
ret = sprdwcn_bus_push_list(dev->tx_data_port,
|
head, tail, tx_count);
|
else
|
ret = sprdwcn_bus_push_list_direct(dev->tx_data_port,
|
head, tail, tx_count);
|
} else {
|
ret = sprdwcn_bus_push_list(dev->tx_data_port,
|
head, tail, tx_count);
|
}
|
|
if (ret != 0) {
|
sprdwcn_bus_list_free(dev->tx_data_port, head, tail, tx_count);
|
sprdwl_add_tx_list_head(tx_list_head, tx_list,
|
ac_index, tx_count);
|
wl_err("%s:%d err Tx data fail\n", __func__, __LINE__);
|
mbufalloc -= tx_count;
|
} else {
|
#if defined(MORE_DEBUG)
|
UPDATE_TX_PACKETS(dev, tx_count, tx_bytes);
|
#endif
|
INIT_LIST_HEAD(tx_list_head);
|
tx_packets += tx_count;
|
wl_info("%s, %d, tx_count=%d, total=%lu, mbufalloc=%lu, mbufpop=%lu\n",
|
__func__, __LINE__, tx_count, tx_packets,
|
mbufalloc, mbufpop);/*TODO*/
|
if (dev->priv->hw_type == SPRDWL_HW_SDIO) {
|
if (!coex_bt_on)
|
sprdwl_add_topop_list(dev->tx_data_port,
|
head, tail, tx_count);
|
}
|
}
|
|
return ret;
|
}
|
|
struct sprdwl_peer_entry
|
*sprdwl_find_peer_entry_using_addr(struct sprdwl_vif *vif, u8 *addr)
|
{
|
struct sprdwl_intf *intf;
|
struct sprdwl_peer_entry *peer_entry = NULL;
|
u8 i;
|
|
intf = (struct sprdwl_intf *)vif->priv->hw_priv;
|
for (i = 0; i < MAX_LUT_NUM; i++) {
|
if (ether_addr_equal(intf->peer_entry[i].tx.da, addr)) {
|
peer_entry = &intf->peer_entry[i];
|
break;
|
}
|
}
|
if (!peer_entry)
|
wl_err("not find peer_entry at :%s\n", __func__);
|
|
return peer_entry;
|
}
|
|
/* It is tx private function, just use in sprdwl_intf_fill_msdu_dscr() */
|
unsigned char sprdwl_find_lut_index(struct sprdwl_intf *intf,
|
struct sprdwl_vif *vif)
|
{
|
u8 ret;
|
unsigned char i;
|
|
if (intf->skb_da == NULL)/*TODO*/
|
goto out;
|
|
wl_debug("%s,bssid: %02x:%02x:%02x:%02x:%02x:%02x\n", __func__,
|
intf->skb_da[0], intf->skb_da[1], intf->skb_da[2],
|
intf->skb_da[3], intf->skb_da[4], intf->skb_da[5]);
|
if (sprdwl_is_group(intf->skb_da) &&
|
(vif->mode == SPRDWL_MODE_AP || vif->mode == SPRDWL_MODE_P2P_GO)) {
|
for (i = 0; i < MAX_LUT_NUM; i++) {
|
if ((sprdwl_is_group(intf->peer_entry[i].tx.da)) &&
|
(intf->peer_entry[i].ctx_id == vif->ctx_id)) {
|
wl_info("%s, %d, group lut_index=%d\n",
|
__func__, __LINE__,
|
intf->peer_entry[i].lut_index);
|
return intf->peer_entry[i].lut_index;
|
}
|
}
|
if (vif->mode == SPRDWL_MODE_AP) {
|
wl_info("%s,AP mode, group bssid,\n"
|
"lut not found, ctx_id:%d, return lut:4\n",
|
__func__, vif->ctx_id);
|
return 4;
|
}
|
if (vif->mode == SPRDWL_MODE_P2P_GO) {
|
wl_info("%s,GO mode, group bssid,\n"
|
"lut not found, ctx_id:%d, return lut:5\n",
|
__func__, vif->ctx_id);
|
return 5;
|
}
|
}
|
|
for (i = 0; i < MAX_LUT_NUM; i++) {
|
if ((0 == memcmp(intf->peer_entry[i].tx.da,
|
intf->skb_da, ETH_ALEN)) &&
|
(intf->peer_entry[i].ctx_id == vif->ctx_id)) {
|
wl_debug("%s, %d, lut_index=%d\n",
|
__func__, __LINE__,
|
intf->peer_entry[i].lut_index);
|
return intf->peer_entry[i].lut_index;
|
}
|
}
|
|
for (i = 0; i < MAX_LUT_NUM; i++) {
|
if ((vif->mode == SPRDWL_MODE_STATION ||
|
vif->mode == SPRDWL_MODE_P2P_CLIENT) &&
|
(intf->peer_entry[i].ctx_id == vif->ctx_id)) {
|
wl_debug("%s, %d, lut_index=%d\n",
|
__func__, __LINE__,
|
intf->peer_entry[i].lut_index);
|
return intf->peer_entry[i].lut_index;
|
}
|
}
|
|
out:
|
switch (vif->mode) {
|
case SPRDWL_MODE_AP:
|
ret = 4;
|
break;
|
|
case SPRDWL_MODE_P2P_GO:
|
ret = 5;
|
break;
|
|
default:
|
ret = 0;
|
break;
|
}
|
|
wl_err("ctx_id = %d, sm_state = %d, bssid =%pM\n",
|
vif->ctx_id, vif->sm_state, intf->skb_da);
|
|
return ret;
|
}
|
|
int sprdwl_intf_fill_msdu_dscr(struct sprdwl_vif *vif,
|
struct sk_buff *skb,
|
u8 type,
|
u8 offset)
|
{
|
u8 protocol;
|
struct tx_msdu_dscr *dscr;
|
struct sprdwl_intf *dev;
|
u8 lut_index;
|
struct sk_buff *temp_skb;
|
unsigned char dscr_rsvd = 0;
|
struct ethhdr *ethhdr = (struct ethhdr *)skb->data;
|
u8 is_special_data = 0;
|
bool is_vowifi2cmd = false;
|
#define DSCR_LEN 11
|
#define MSG_PTR_LEN 8
|
|
if (ethhdr->h_proto == htons(ETH_P_ARP) ||
|
ethhdr->h_proto == htons(ETH_P_TDLS) ||
|
ethhdr->h_proto == htons(ETH_P_PREAUTH))
|
is_special_data = 1;
|
else if ((type == SPRDWL_TYPE_CMD) &&
|
is_vowifi_pkt(skb, &is_vowifi2cmd))
|
is_special_data = 1;
|
|
dev = (struct sprdwl_intf *)(vif->priv->hw_priv);
|
dscr_rsvd = INTF_IS_PCIE ? MSDU_DSCR_RSVD : 0;
|
if (skb_headroom(skb) < (DSCR_LEN + dev->hif_offset +
|
MSG_PTR_LEN + dscr_rsvd)) {
|
temp_skb = skb;
|
|
skb = skb_realloc_headroom(skb, (DSCR_LEN + dev->hif_offset +
|
MSG_PTR_LEN + dscr_rsvd));
|
kfree_skb(temp_skb);
|
if (skb == NULL) {
|
wl_err("%s:%d failed to unshare skbuff: NULL\n",
|
__func__, __LINE__);
|
return -EPERM;
|
}
|
#if defined(MORE_DEBUG)
|
dev->stats.tx_realloc++;
|
#endif
|
}
|
|
dev->skb_da = skb->data;
|
|
lut_index = sprdwl_find_lut_index(dev, vif);
|
if ((lut_index < 6) && (!sprdwl_is_group(dev->skb_da))) {
|
wl_err("%s, %d, sta disconn, no data tx!", __func__, __LINE__);
|
return -EPERM;
|
}
|
skb_push(skb, sizeof(struct tx_msdu_dscr) + offset + dscr_rsvd);
|
dscr = (struct tx_msdu_dscr *)(skb->data);
|
memset(dscr, 0x00, sizeof(struct tx_msdu_dscr));
|
dscr->common.type = (type == SPRDWL_TYPE_CMD ?
|
SPRDWL_TYPE_CMD : SPRDWL_TYPE_DATA);
|
dscr->common.direction_ind = 0;
|
dscr->common.need_rsp = 0;/*TODO*/
|
dscr->common.interface = vif->ctx_id;
|
dscr->pkt_len = cpu_to_le16(skb->len - DSCR_LEN - dscr_rsvd);
|
dscr->offset = DSCR_LEN;
|
/*TODO*/
|
dscr->tx_ctrl.sw_rate = (is_special_data == 1 ? 1 : 0);
|
dscr->tx_ctrl.wds = 0; /*TBD*/
|
dscr->tx_ctrl.swq_flag = 0; /*TBD*/
|
dscr->tx_ctrl.rsvd = 0; /*TBD*/
|
dscr->tx_ctrl.next_buffer_type = 0;
|
dscr->tx_ctrl.pcie_mh_readcomp = 0;
|
dscr->buffer_info.msdu_tid = 0;
|
dscr->buffer_info.mac_data_offset = 0;
|
dscr->sta_lut_index = lut_index;
|
|
if (skb->ip_summed == CHECKSUM_PARTIAL) {
|
dscr->tx_ctrl.checksum_offload = 1;
|
if (ethhdr->h_proto == htons(ETH_P_IPV6))
|
protocol = ipv6_hdr(skb)->nexthdr;
|
else
|
protocol = ip_hdr(skb)->protocol;
|
|
dscr->tx_ctrl.checksum_type =
|
protocol == IPPROTO_TCP ? 1 : 0;
|
dscr->tcp_udp_header_offset =
|
skb->transport_header - skb->mac_header;
|
wl_debug("%s: offload: offset: %d, protocol: %d\n",
|
__func__, dscr->tcp_udp_header_offset, protocol);
|
}
|
|
return 0;
|
}
|
|
#if defined FPGA_LOOPBACK_TEST
|
unsigned char tx_ipv4_udp[] = {
|
0x02, 0x04, 0x00, 0x01, 0x00, 0x06, 0x40, 0x45,
|
0xda, 0xf0, 0xff, 0x7e, 0x08, 0x00, 0x45, 0x00,
|
0x00, 0xe4, 0xc8, 0xc0, 0x40, 0x00, 0x40, 0x11,
|
0x8d, 0xb2, 0xc0, 0xa8, 0x31, 0x01, 0xc0, 0xa8,
|
0x31, 0x44, 0x67, 0x62, 0x3c, 0xbe, 0x00, 0xd0,
|
0xa0, 0x26, 0x80, 0x21, 0x81, 0x4b, 0x03, 0x68,
|
0x2b, 0x37, 0xde, 0xad, 0xbe, 0xef, 0x47, 0x10,
|
0x11, 0x35, 0xb1, 0x00, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00,
|
0x00, 0x00, 0xAA, 0xBB};
|
|
unsigned char tx_arp[] = {
|
0x02, 0x00, 0x00, 0x01, 0x00, 0x06, 0x74, 0x27,
|
0xea, 0xc8, 0x3e, 0x69, 0x08, 0x06, 0x00, 0x01,
|
0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x74, 0x27,
|
0xea, 0xc8, 0x3e, 0x69, 0xc0, 0xa8, 0x01, 0x14,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8,
|
0x01, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb};
|
|
int sprdwl_intf_tx_data_fpga_test(struct sprdwl_intf *intf,
|
unsigned char *data, int len)
|
{
|
struct sprdwl_msg_buf *msg;
|
struct sk_buff *skb;
|
int ret;
|
|
wl_debug("%s: #%d start\n", __func__, intf->loopback_n);
|
msg = sprdwl_get_msg_buf(intf, SPRDWL_TYPE_DATA,
|
SPRDWL_MODE_STATION, 1);
|
if (!msg) {
|
wl_err("%s:%d get msg buf failed\n", __func__, __LINE__);
|
return -1;
|
}
|
if (data == NULL) {
|
skb = dev_alloc_skb(244 + NET_IP_ALIGN);
|
skb_reserve(skb, NET_IP_ALIGN);
|
memcpy(skb->data, tx_ipv4_udp, 244);
|
skb_put(skb, 244);
|
} else {
|
skb = dev_alloc_skb(len + NET_IP_ALIGN);
|
skb_reserve(skb, NET_IP_ALIGN);
|
memcpy(skb->data, data, len);
|
skb_put(skb, len);
|
}
|
ret = sprdwl_send_data_fpga_test(intf->priv,
|
msg, skb, SPRDWL_TYPE_DATA, 0);
|
wl_debug("%s:%d loopback_n#%d end ret=%d\n", __func__, __LINE__,
|
intf->loopback_n, ret);
|
intf->loopback_n++;
|
return ret;
|
}
|
|
int sprdwl_intf_fill_msdu_dscr_test(struct sprdwl_priv *priv,
|
struct sk_buff *skb,
|
u8 type,
|
u8 offset)
|
{
|
struct tx_msdu_dscr *dscr;
|
struct sprdwl_intf *dev;
|
u8 lut_index;
|
struct sk_buff *temp_skb;
|
#define DSCR_LEN 11
|
#define MSG_PTR_LEN 8
|
|
if (skb_headroom(skb) < (DSCR_LEN + intf->hif_offset + MSG_PTR_LEN)) {
|
temp_skb = skb;
|
|
skb = skb_realloc_headroom(skb, (DSCR_LEN + intf->hif_offset +
|
MSG_PTR_LEN));
|
kfree_skb(temp_skb);
|
if (skb == NULL) {
|
wl_err("%s:%d failed to realloc skbuff: NULL\n",
|
__func__, __LINE__);
|
return 0;
|
}
|
}
|
dev = (struct sprdwl_intf *)(priv->hw_priv);
|
dev->skb_da = skb->data;
|
lut_index = sprdwl_find_index_using_addr(dev);
|
skb_push(skb, sizeof(struct tx_msdu_dscr) + offset);
|
dscr = (struct tx_msdu_dscr *)(skb->data);
|
dscr->common.type = type;
|
dscr->pkt_len = cpu_to_le16(skb->len - (DSCR_LEN));
|
dscr->offset = DSCR_LEN;
|
dscr->tx_ctrl.checksum_offload = 1;
|
dscr->tx_ctrl.checksum_type =
|
ip_hdr(skb)->protocol == IPPROTO_TCP ? 1 : 0;
|
dscr->tx_ctrl.sw_rate = (type == SPRDWL_TYPE_DATA ? 0 : 1);
|
dscr->tx_ctrl.wds = 0;
|
dscr->sta_lut_index = lut_index;
|
dscr->tcp_udp_header_offset = 34;
|
return 1;
|
}
|
#endif
|
|
int sprdwl_rx_fill_mbuf(struct mbuf_t *head, struct mbuf_t *tail, int num, int len)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
int ret = 0, count = 0;
|
struct mbuf_t *pos = NULL;
|
|
for (pos = head, count = 0; count < num; count++) {
|
pos->len = ALIGN(len, SMP_CACHE_BYTES);
|
pos->buf = netdev_alloc_frag(pos->len);
|
pos->phy = mm_virt_to_phys(&intf->pdev->dev, pos->buf,
|
pos->len, DMA_FROM_DEVICE);
|
if (unlikely(!pos->buf)) {
|
ret = -ENOMEM;
|
break;
|
}
|
pos = pos->next;
|
}
|
|
if (ret) {
|
pos = head;
|
while (count--) {
|
sprdwl_free_data(pos->buf, SPRDWL_DEFRAG_MEM);
|
pos = pos->next;
|
}
|
}
|
|
return ret;
|
}
|
|
int sprdwl_rx_common_push(int chn, struct mbuf_t **head, struct mbuf_t **tail,
|
int *num, int len)
|
{
|
int ret = 0;
|
|
ret = sprdwcn_bus_list_alloc(chn, head, tail, num);
|
if (ret || head == NULL) {
|
wl_err("%s:%d sprdwcn_bus_list_alloc fail\n", __func__, __LINE__);
|
ret = -ENOMEM;
|
} else {
|
ret = sprdwl_rx_fill_mbuf(*head, *tail, *num, len);
|
if (ret) {
|
wl_err("%s: alloc buf fail\n", __func__);
|
sprdwcn_bus_list_free(chn, *head, *tail, *num);
|
*head = NULL;
|
*tail = NULL;
|
*num = 0;
|
}
|
}
|
|
return ret;
|
}
|
|
inline void *sprdwl_get_rx_data(struct sprdwl_intf *intf,
|
void *pos, void **data,
|
void **tran_data, int *len, int offset)
|
{
|
struct mbuf_t *mbuf = (struct mbuf_t *)pos;
|
|
if (intf->priv->hw_type == SPRDWL_HW_PCIE) {
|
mm_phys_to_virt(&intf->pdev->dev, mbuf->phy, mbuf->len,
|
DMA_FROM_DEVICE, false);
|
mbuf->phy = 0;
|
}
|
|
*tran_data = mbuf->buf;
|
*data = (*tran_data) + offset;
|
*len = mbuf->len;
|
mbuf->buf = NULL;
|
|
return (void *)mbuf->next;
|
}
|
|
inline void sprdwl_free_rx_data(struct sprdwl_intf *intf,
|
int chn, void *head, void *tail, int num)
|
{
|
int len = 0, ret = 0;
|
|
/* We should refill mbuf in pcie mode */
|
if (intf->priv->hw_type == SPRDWL_HW_PCIE) {
|
if (intf->rx_cmd_port == chn)
|
len = SPRDWL_MAX_CMD_RXLEN;
|
else
|
len = SPRDWL_MAX_DATA_RXLEN;
|
|
ret = sprdwl_rx_fill_mbuf(head, tail, num, len);
|
if (ret) {
|
wl_err("%s: alloc buf fail\n", __func__);
|
sprdwcn_bus_list_free(chn, (struct mbuf_t *)head,
|
(struct mbuf_t *)tail, num);
|
head = NULL;
|
tail = NULL;
|
num = 0;
|
}
|
}
|
|
if (!ret)
|
sprdwcn_bus_push_list(chn, (struct mbuf_t *)head, (struct mbuf_t *)tail, num);
|
}
|
|
void sprdwl_count_rx_tp(struct sprdwl_rx_if *rx_if, int num)
|
{
|
long long timeus = 0;
|
struct sprdwl_intf *intf = get_intf();
|
|
rx_if->rx_data_num += num;
|
if (rx_if->rx_data_num == num) {
|
rx_if->rxtimebegin = ktime_get();
|
return;
|
}
|
|
rx_if->rxtimeend = ktime_get();
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
timeus = div_u64(rx_if->rxtimeend.tv64 - rx_if->rxtimebegin.tv64, NSEC_PER_USEC);
|
#else
|
timeus = div_u64(rx_if->rxtimeend - rx_if->rxtimebegin, NSEC_PER_USEC);
|
#endif
|
|
if(!timeus) {
|
return;
|
}
|
|
if (div_u64((rx_if->rx_data_num * 1000), timeus) >= intf->rxnum_level &&
|
rx_if->rx_data_num >= 1000) {
|
rx_if->rx_data_num = 0;
|
#ifdef CPUFREQ_UPDATE_SUPPORT
|
sprdwl_boost();
|
#endif /* CPUFREQ_UPDATE_SUPPORT */
|
} else if (timeus >= USEC_PER_SEC) {
|
rx_if->rx_data_num = 0;
|
}
|
}
|
|
static int check_msdu_early(struct sprdwl_intf *intf, struct mbuf_t *mbuf)
|
{
|
struct rx_msdu_desc *msdu_desc =
|
(struct rx_msdu_desc *)(mbuf->buf + intf->hif_offset);
|
|
if (mbuf->len < msdu_desc->msdu_len ||
|
msdu_desc->msdu_len > 1600) {
|
wl_err("%s, %d, %d, %d\n", __func__, __LINE__, mbuf->len, msdu_desc->msdu_len);
|
return -1;
|
}
|
return 0;
|
}
|
|
static int intf_rx_handle(int chn, struct mbuf_t *head,
|
struct mbuf_t *tail, int num)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
struct sprdwl_rx_if *rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx;
|
struct sprdwl_msg_buf *msg = NULL;
|
|
wl_debug("%s: channel:%d head:%p tail:%p num:%d\n",
|
__func__, chn, head, tail, num);
|
if ((intf->priv->hw_type == SPRDWL_HW_SDIO && chn == SDIO_RX_DATA_PORT) ||
|
(intf->priv->hw_type == SPRDWL_HW_USB && chn == USB_RX_DATA_PORT))
|
sprdwl_count_rx_tp(rx_if, num);
|
|
/*To process credit earlier*/
|
if (intf->priv->hw_type == SPRDWL_HW_SDIO ||
|
intf->priv->hw_type == SPRDWL_HW_USB) {
|
unsigned int i = 0;
|
struct mbuf_t *mbuf = NULL;
|
|
mbuf = head;
|
for (i = num; i > 0; i--) {
|
sprdwl_sdio_process_credit(intf,
|
(void *)(mbuf->buf + intf->hif_offset));
|
if (intf->priv->hw_type == SPRDWL_HW_USB &&
|
chn == USB_RX_DATA_PORT &&
|
check_msdu_early(intf, mbuf)) {
|
sprdwcn_bus_push_list(chn, head, tail, num);
|
return 0;
|
}
|
|
mbuf = mbuf->next;
|
}
|
}
|
|
/* FIXME: Should we use replace msg? */
|
msg = sprdwl_alloc_msg_buf(&rx_if->rx_list);
|
if (!msg) {
|
wl_err("%s: no msgbuf\n", __func__);
|
sprdwcn_bus_push_list(chn, head, tail, num);
|
return 0;
|
}
|
|
sprdwl_fill_msg(msg, NULL, (void *)head, num);
|
msg->fifo_id = chn;
|
msg->buffer_type = SPRDWL_DEFRAG_MEM;
|
msg->data = (void *)tail;
|
|
sprdwl_queue_msg_buf(msg, &rx_if->rx_list);
|
#ifdef SPRD_RX_THREAD
|
rx_up(rx_if);
|
#else
|
queue_work(rx_if->rx_queue, &rx_if->rx_work);
|
#endif
|
return 0;
|
}
|
|
void sprdwl_handle_pop_list(void *data)
|
{
|
int i;
|
struct sprdwl_msg_buf *msg_pos;
|
struct mbuf_t *mbuf_pos = NULL;
|
struct sprdwl_pop_work *pop = (struct sprdwl_pop_work *)data;
|
struct sprdwl_tx_msg *tx_msg;
|
struct sprdwl_intf *intf = get_intf();
|
struct list_head tmp_list;
|
struct sprdwl_msg_buf *msg_head, *msg_tail;
|
|
tx_msg = (struct sprdwl_tx_msg *)intf->sprdwl_tx;
|
mbuf_pos = (struct mbuf_t *)pop->head;
|
msg_pos = GET_MSG_BUF(mbuf_pos);
|
|
msg_head = GET_MSG_BUF((struct mbuf_t *)pop->head);
|
msg_tail = GET_MSG_BUF((struct mbuf_t *)pop->tail);
|
|
spin_lock_bh(&tx_msg->xmit_msg_list.free_lock);
|
list_cut_position(&tmp_list, msg_head->list.prev, &msg_tail->list);
|
spin_unlock_bh(&tx_msg->xmit_msg_list.free_lock);
|
|
for (i = 0; i < pop->num; i++) {
|
msg_pos = GET_MSG_BUF(mbuf_pos);
|
dev_kfree_skb(msg_pos->skb);
|
mbuf_pos = mbuf_pos->next;
|
}
|
|
spin_lock_bh(&tx_msg->tx_list_qos_pool.freelock);
|
list_splice_tail(&tmp_list, &msg_pos->msglist->freelist);
|
spin_unlock_bh(&tx_msg->tx_list_qos_pool.freelock);
|
sprdwcn_bus_list_free(pop->chn,
|
pop->head,
|
pop->tail,
|
pop->num);
|
mbufpop += pop->num;
|
}
|
|
/*call back func for HIF pop_link*/
|
int sprdwl_tx_data_pop_list(int channel, struct mbuf_t *head, struct mbuf_t *tail, int num)
|
{
|
struct mbuf_t *mbuf_pos = NULL;
|
#if defined(MORE_DEBUG)
|
struct sprdwl_msg_buf *msg_head;
|
#endif
|
struct sprdwl_intf *intf = get_intf();
|
|
wl_debug("%s channel: %d, head: %p, tail: %p num: %d\n",
|
__func__, channel, head, tail, num);
|
|
if (intf->priv->hw_type == SPRDWL_HW_PCIE) {
|
/* FIXME: Temp solution, addr node pos hard to sync dma */
|
for (mbuf_pos = head; mbuf_pos != NULL;
|
mbuf_pos = mbuf_pos->next) {
|
mm_phys_to_virt(&intf->pdev->dev, mbuf_pos->phy,
|
mbuf_pos->len, DMA_TO_DEVICE, false);
|
mbuf_pos->phy = 0;
|
kfree(mbuf_pos->buf);
|
mbuf_pos->buf = NULL;
|
if (--num == 0)
|
break;
|
}
|
sprdwcn_bus_list_free(channel, head, tail, num);
|
wl_info("%s:%d free : %d msg buf\n", __func__, __LINE__, num);
|
return 0;
|
}
|
#if defined(MORE_DEBUG)
|
msg_head = GET_MSG_BUF(head);
|
/*show packet average sent time, unit: ns*/
|
sprdwl_get_tx_avg_time(intf, msg_head->tx_start_time);
|
#endif
|
|
sprdwl_add_topop_list(channel, head, tail, num);
|
wl_debug("%s:%d free : %d msg buf\n", __func__, __LINE__, num);
|
|
return 0;
|
}
|
|
/*free PCIe data when receive txc event from cp*/
|
int sprdwl_tx_free_pcie_data(struct sprdwl_intf *dev, unsigned char *data,
|
unsigned short len)
|
{
|
int i;
|
struct sprdwl_tx_msg *tx_msg;
|
unsigned char *data_addr_ptr;
|
unsigned long pcie_addr, timeout;
|
unsigned short data_num;
|
struct list_head *free_list;
|
struct txc_addr_buff *txc_addr;
|
unsigned char (*pos)[5];
|
struct sprdwl_msg_buf *msg_buf, *pos_buf, *temp_buf;
|
#if defined(MORE_DEBUG)
|
unsigned long tx_start_time = 0;
|
#endif
|
|
txc_addr = (struct txc_addr_buff *)data;
|
data_num = txc_addr->number;
|
pos = (unsigned char (*)[5])(txc_addr + 1);
|
for (i = 0; i < data_num; i++, pos++) {
|
memcpy(&pcie_addr, pos, SPRDWL_PHYS_LEN);
|
data_addr_ptr = (unsigned char *)
|
mm_phys_to_virt(&dev->pdev->dev, pcie_addr,
|
SPRDWL_MAX_DATA_TXLEN, DMA_TO_DEVICE, true);
|
msg_buf = (struct sprdwl_msg_buf *)
|
(data_addr_ptr - (sizeof(unsigned long *) + dev->hif_offset));
|
#if defined(MORE_DEBUG)
|
if (i == 0)
|
tx_start_time = msg_buf->tx_start_time;
|
#endif
|
dev_kfree_skb(msg_buf->skb);
|
sprdwl_dequeue_data_buf(msg_buf);
|
}
|
#if defined(MORE_DEBUG)
|
sprdwl_get_tx_avg_time(dev, tx_start_time);
|
#endif
|
tx_msg = (struct sprdwl_tx_msg *)dev->sprdwl_tx;
|
free_list = &tx_msg->xmit_msg_list.to_free_list;
|
/*if cp fail to sent txc event, data will be freed timeout*/
|
if (!list_empty(free_list)) {
|
timeout = msecs_to_jiffies(SPRDWL_TX_DATA_TIMEOUT);
|
list_for_each_entry_safe(pos_buf,
|
temp_buf, free_list, list) {
|
if (time_after(jiffies, pos_buf->timeout + timeout)) {
|
dev_kfree_skb(pos_buf->skb);
|
sprdwl_dequeue_data_buf(pos_buf);
|
} else {
|
return 0;
|
}
|
}
|
}
|
return 0;
|
}
|
|
int sprdwl_tx_cmd_pop_list(int channel, struct mbuf_t *head, struct mbuf_t *tail, int num)
|
{
|
int count = 0;
|
struct mbuf_t *pos = NULL;
|
struct sprdwl_intf *intf = get_intf();
|
struct sprdwl_tx_msg *tx_msg;
|
struct sprdwl_msg_buf *pos_buf, *temp_buf;
|
|
wl_debug("%s channel: %d, head: %p, tail: %p num: %d\n",
|
__func__, channel, head, tail, num);
|
|
tx_msg = (struct sprdwl_tx_msg *)intf->sprdwl_tx;
|
|
wl_debug("%s len: %d buf: %s\n", __func__, head->len, head->buf + 4);
|
|
pos = head;
|
|
list_for_each_entry_safe(pos_buf, temp_buf,
|
&tx_msg->tx_list_cmd.cmd_to_free, list) {
|
if (pos_buf->tran_data == pos->buf) {
|
wl_debug("move CMD node from to_free to free list\n");
|
/*list msg_buf from to_free list to free list*/
|
sprdwl_free_cmd_buf(pos_buf, &tx_msg->tx_list_cmd);
|
|
if (intf->priv->hw_type == SPRDWL_HW_PCIE) {
|
mm_phys_to_virt(&intf->pdev->dev, pos->phy,
|
pos->len, DMA_TO_DEVICE, false);
|
pos->phy = 0;
|
}
|
/*free it*/
|
kfree(pos->buf);
|
pos->buf = NULL;
|
pos = pos->next;
|
count++;
|
}
|
if (count == num)
|
break;
|
}
|
|
tx_msg->cmd_poped += num;
|
mbufpop += num;
|
wl_info("tx_cmd_pop add num: %d=cmd_poped%d, cmd_send%d\n",
|
num, tx_msg->cmd_poped, tx_msg->cmd_send);
|
sprdwcn_bus_list_free(channel, head, tail, num);
|
|
return 0;
|
}
|
|
int sprdwl_rx_cmd_push(int chn, struct mbuf_t **head, struct mbuf_t **tail, int *num)
|
{
|
return sprdwl_rx_common_push(chn, head, tail,
|
num, SPRDWL_MAX_CMD_RXLEN);
|
}
|
|
int sprdwl_rx_data_push(int chn, struct mbuf_t **head, struct mbuf_t **tail, int *num)
|
{
|
return sprdwl_rx_common_push(chn, head, tail,
|
num, SPRDWL_MAX_DATA_RXLEN);
|
}
|
|
/*
|
* mode:
|
* 0 - suspend
|
* 1 - resume
|
*/
|
int sprdwl_suspend_resume_handle(int chn, int mode)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
struct sprdwl_priv *priv = intf->priv;
|
struct sprdwl_tx_msg *tx_msg = (struct sprdwl_tx_msg *)intf->sprdwl_tx;
|
int ret = -EBUSY;
|
struct sprdwl_vif *vif;
|
struct timespec time;
|
enum sprdwl_mode sprdwl_mode = SPRDWL_MODE_STATION;
|
u8 mode_found = 0;
|
|
for (sprdwl_mode = SPRDWL_MODE_STATION; sprdwl_mode < SPRDWL_MODE_MAX; sprdwl_mode++) {
|
if (priv->fw_stat[sprdwl_mode] == SPRDWL_INTF_OPEN) {
|
mode_found = 1;
|
break;
|
}
|
}
|
|
if (0 == mode_found) {
|
wl_err("%s suspend failed, mode not found\n", __func__);
|
return -EBUSY;
|
}
|
|
vif = mode_to_vif(priv, sprdwl_mode);
|
if (vif == NULL || intf->cp_asserted) {
|
wl_err("%s, %d, error! NULL vif or assert\n", __func__, __LINE__);
|
sprdwl_put_vif(vif);
|
return -EBUSY;
|
}
|
|
if (mode == 0) {
|
if (atomic_read(&tx_msg->tx_list_qos_pool.ref) > 0 ||
|
atomic_read(&tx_msg->tx_list_cmd.ref) > 0 ||
|
!list_empty(&tx_msg->xmit_msg_list.to_send_list) ||
|
!list_empty(&tx_msg->xmit_msg_list.to_free_list)) {
|
wl_info("%s, %d,Q not empty suspend not allowed\n",
|
__func__, __LINE__);
|
sprdwl_put_vif(vif);
|
return -EBUSY;
|
}
|
priv->wakeup_tracer.resume_flag = 0;
|
intf->suspend_mode = SPRDWL_PS_SUSPENDING;
|
|
if ((vif->mode == SPRDWL_MODE_STATION) && (vif->sm_state == SPRDWL_CONNECTED)
|
&& (sprdwcn_bus_get_wl_wake_host_en() == SPRDWL_NO_WAKE_HOST)) {
|
vif->suspend_resume_connect.reconnect_flag = true;
|
priv->is_suspending = 1;
|
sprdwl_cfg80211_disconnect(NULL, vif->ndev, 0);
|
}
|
|
/* if cp2 is wakeup, send power_down firstly */
|
if (intf->fw_power_down != 1) {
|
priv->is_suspending = 1;
|
sprdwl_fw_power_down_ack(vif->priv, vif->ctx_id);
|
}
|
|
getnstimeofday(&time);
|
intf->sleep_time = timespec_to_ns(&time);
|
priv->is_suspending = 1;
|
ret = sprdwl_power_save(priv,
|
vif->ctx_id,
|
SPRDWL_SUSPEND_RESUME,
|
0);
|
if (ret == 0)
|
intf->suspend_mode = SPRDWL_PS_SUSPENDED;
|
else
|
intf->suspend_mode = SPRDWL_PS_RESUMED;
|
|
wl_info("%s, %d,suspend ret=%d\n", __func__, __LINE__, ret);
|
} else if (mode == 1) {
|
intf->suspend_mode = SPRDWL_PS_RESUMING;
|
priv->wakeup_tracer.resume_flag = 1;
|
|
complete(&intf->suspend_completed);
|
|
getnstimeofday(&time);
|
intf->sleep_time = timespec_to_ns(&time) - intf->sleep_time;
|
ret = sprdwl_power_save(priv,
|
vif->ctx_id,
|
SPRDWL_SUSPEND_RESUME,
|
1);
|
wl_info("%s, %d,resume ret=%d, resume after %lu ms\n",
|
__func__, __LINE__,
|
ret, intf->sleep_time/1000000);
|
|
if ((vif->mode == SPRDWL_MODE_STATION) && (vif->suspend_resume_connect.reconnect_flag == true)) {
|
sprdwl_cfg80211_connect(priv->wiphy, vif->ndev, &vif->suspend_resume_connect.connect_params);
|
vif->suspend_resume_connect.reconnect_flag = false;
|
}
|
}
|
sprdwl_put_vif(vif);
|
return ret;
|
}
|
|
/* SDIO TX:
|
* Type 3:WIFI
|
* Subtype 0 --> port 8
|
* Subtype 1 --> port 9
|
* Subtype 2 --> port 10(fifolen=8)
|
* Subtype 3 --> port 11(fifolen=8)
|
* SDIO RX:
|
* Type 3:WIFI
|
* Subtype 0 --> port 10
|
* Subtype 1 --> port 11
|
* Subtype 2 --> port 12(fifolen=8)
|
* Subtype 3 --> port 13(fifolen=8)
|
*/
|
struct mchn_ops_t sdio_hif_ops[] = {
|
/* RX INTF */
|
/* NOTE: Requested by SDIO team, pool_size MUST be 1 in RX */
|
INIT_INTF(SDIO_RX_CMD_PORT, 0, 0, 0,
|
SPRDWL_MAX_CMD_RXLEN, 1, 0, 0, 0,
|
intf_rx_handle, NULL, NULL, NULL),
|
INIT_INTF(SDIO_RX_PKT_LOG_PORT, 0, 0, 0,
|
SPRDWL_MAX_DATA_RXLEN, 1, 0, 0, 0,
|
intf_rx_handle, NULL, NULL, NULL),
|
INIT_INTF(SDIO_RX_DATA_PORT, 0, 0, 0,
|
SPRDWL_MAX_DATA_RXLEN, 1, 0, 0, 0,
|
intf_rx_handle, NULL, NULL, NULL),
|
|
/* TX INTF */
|
INIT_INTF(SDIO_TX_CMD_PORT, 0, 1, 0,
|
SPRDWL_MAX_CMD_TXLEN, 10, 0, 0, 0,
|
sprdwl_tx_cmd_pop_list, NULL, NULL,
|
sprdwl_suspend_resume_handle),
|
INIT_INTF(SDIO_TX_DATA_PORT, 0, 1, 0,
|
SPRDWL_MAX_DATA_TXLEN, 800, 0, 0, 0,
|
sprdwl_tx_data_pop_list, NULL, NULL, NULL),
|
};
|
|
struct mchn_ops_t pcie_hif_ops[] = {
|
/* RX INTF */
|
INIT_INTF(PCIE_RX_CMD_PORT, 1, 0, 0,
|
SPRDWL_MAX_CMD_RXLEN, 1, 0, 0, 0,
|
intf_rx_handle,
|
sprdwl_rx_cmd_push, NULL, NULL),
|
INIT_INTF(PCIE_RX_DATA_PORT, 1, 0, 0,
|
SPRDWL_MAX_DATA_RXLEN, 1, 0, 0, 0,
|
intf_rx_handle,
|
sprdwl_rx_data_push, NULL, NULL),
|
/* TX INTF */
|
INIT_INTF(SDIO_TX_CMD_PORT, 1, 1, 0,
|
SPRDWL_MAX_CMD_TXLEN, 10, 0, 0, 0,
|
sprdwl_tx_cmd_pop_list, NULL, NULL, NULL),
|
INIT_INTF(SDIO_TX_DATA_PORT, 1, 1, 0,
|
SPRDWL_MAX_DATA_TXLEN, 300, 0, 0, 0,
|
sprdwl_tx_data_pop_list, NULL, NULL, NULL)
|
};
|
|
struct mchn_ops_t usb_hif_ops[] = {
|
/* RX INTF */
|
INIT_INTF(USB_RX_CMD_PORT, 3, 0, 0,
|
SPRDWL_MAX_CMD_RXLEN, 10, 0, 0, 0,
|
intf_rx_handle, NULL, NULL, NULL),
|
INIT_INTF(USB_RX_PKT_LOG_PORT, 3, 0, 0,
|
SPRDWL_MAX_DATA_RXLEN, 50, 0, 0, 0,
|
intf_rx_handle, NULL, NULL, NULL),
|
INIT_INTF(USB_RX_DATA_PORT, 3, 0, 0,
|
SPRDWL_MAX_DATA_RXLEN, 1000, 0, 0, 0,
|
intf_rx_handle, NULL, NULL, NULL),
|
|
/* TX INTF */
|
INIT_INTF(USB_TX_CMD_PORT, 3, 1, 0,
|
SPRDWL_MAX_CMD_TXLEN, 100, 0, 0, 0,
|
sprdwl_tx_cmd_pop_list, NULL, NULL,
|
sprdwl_suspend_resume_handle),
|
INIT_INTF(USB_TX_DATA_PORT, 3, 1, 0,
|
SPRDWL_MAX_DATA_TXLEN, 300, 0, 0, 0,
|
sprdwl_tx_data_pop_list, NULL, NULL, NULL),
|
};
|
|
struct sprdwl_peer_entry
|
*sprdwl_find_peer_entry_using_lut_index(struct sprdwl_intf *intf,
|
unsigned char sta_lut_index)
|
{
|
int i = 0;
|
struct sprdwl_peer_entry *peer_entry = NULL;
|
|
for (i = 0; i < MAX_LUT_NUM; i++) {
|
if (sta_lut_index == intf->peer_entry[i].lut_index) {
|
peer_entry = &intf->peer_entry[i];
|
break;
|
}
|
}
|
|
return peer_entry;
|
}
|
|
/* update lut-inidex if event_sta_lut received
|
* at CP side, lut_index range 0-31
|
* but 0-3 were used to send non-assoc frame(only used by CP)
|
* so for Ap-CP interface, there is only 4-31
|
*/
|
void sprdwl_event_sta_lut(struct sprdwl_vif *vif, u8 *data, u16 len)
|
{
|
struct sprdwl_intf *intf;
|
struct sprdwl_sta_lut_ind *sta_lut = NULL;
|
u8 i;
|
|
if (len < sizeof(*sta_lut)) {
|
wl_err("%s, len:%d too short!\n", __func__, len);
|
return;
|
}
|
intf = (struct sprdwl_intf *)vif->priv->hw_priv;
|
sta_lut = (struct sprdwl_sta_lut_ind *)data;
|
if (intf != get_intf()) {
|
wl_err("%s, wrong intf!\n", __func__);
|
return;
|
}
|
if (sta_lut == NULL) {
|
wl_err("%s, NULL input data!\n", __func__);
|
return;
|
}
|
|
i = sta_lut->sta_lut_index;
|
|
wl_debug("ctx_id:%d,action:%d,lut:%d\n", sta_lut->ctx_id,
|
sta_lut->action, sta_lut->sta_lut_index);
|
switch (sta_lut->action) {
|
case DEL_LUT_INDEX:
|
if (intf->peer_entry[i].ba_tx_done_map != 0) {
|
intf->peer_entry[i].ht_enable = 0;
|
intf->peer_entry[i].ip_acquired = 0;
|
intf->peer_entry[i].ba_tx_done_map = 0;
|
/*sprdwl_tx_delba(intf, intf->peer_entry + i);*/
|
}
|
peer_entry_delba((void *)intf, i);
|
memset(&intf->peer_entry[i], 0x00,
|
sizeof(struct sprdwl_peer_entry));
|
intf->peer_entry[i].ctx_id = 0xFF;
|
intf->tx_num[i] = 0;
|
sprdwl_dis_flush_txlist(intf, i);
|
break;
|
case UPD_LUT_INDEX:
|
peer_entry_delba((void *)intf, i);
|
sprdwl_dis_flush_txlist(intf, i);
|
case ADD_LUT_INDEX:
|
intf->peer_entry[i].lut_index = i;
|
intf->peer_entry[i].ctx_id = sta_lut->ctx_id;
|
intf->peer_entry[i].ht_enable = sta_lut->is_ht_enable;
|
intf->peer_entry[i].vht_enable = sta_lut->is_vht_enable;
|
intf->peer_entry[i].ba_tx_done_map = 0;
|
intf->tx_num[i] = 0;
|
|
wl_debug("ctx_id%d,action%d,lut%d,%x:%x:%x:%x:%x:%x\n",
|
sta_lut->ctx_id, sta_lut->action,
|
sta_lut->sta_lut_index,
|
sta_lut->ra[0], sta_lut->ra[1], sta_lut->ra[2],
|
sta_lut->ra[3], sta_lut->ra[4], sta_lut->ra[5]);
|
ether_addr_copy(intf->peer_entry[i].tx.da, sta_lut->ra);
|
break;
|
default:
|
break;
|
}
|
}
|
|
void sprdwl_tx_ba_mgmt(struct sprdwl_priv *priv, void *data, int len,
|
unsigned char cmd_id, unsigned char ctx_id)
|
{
|
struct sprdwl_msg_buf *msg;
|
unsigned char *data_ptr;
|
u8 *rbuf;
|
u16 rlen = (1 + sizeof(struct host_addba_param));
|
|
msg = sprdwl_cmd_getbuf(priv, len, ctx_id, SPRDWL_HEAD_RSP,
|
cmd_id);
|
if (!msg) {
|
wl_err("%s, %d, get msg err\n", __func__, __LINE__);
|
return;
|
}
|
rbuf = kzalloc(rlen, GFP_KERNEL);
|
if (!rbuf) {
|
wl_err("%s, %d, alloc rbuf err\n", __func__, __LINE__);
|
return;
|
}
|
memcpy(msg->data, data, len);
|
data_ptr = (unsigned char *)data;
|
|
if (sprdwl_debug_level >= L_DBG)
|
sprdwl_hex_dump("sprdwl_tx_ba_mgmt", data_ptr, len);
|
|
if (sprdwl_cmd_send_recv(priv, msg, CMD_WAIT_TIMEOUT, rbuf, &rlen))
|
goto out;
|
/*if tx ba req failed, need to clear txba map*/
|
if (cmd_id == WIFI_CMD_ADDBA_REQ &&
|
rbuf[0] != ADDBA_REQ_RESULT_SUCCESS) {
|
struct host_addba_param *addba;
|
struct sprdwl_peer_entry *peer_entry = NULL;
|
struct sprdwl_intf *intf = get_intf();
|
u16 tid = 0;
|
|
addba = (struct host_addba_param *)(rbuf + 1);
|
peer_entry = &intf->peer_entry[addba->lut_index];
|
tid = addba->addba_param.tid;
|
if (!test_and_clear_bit(tid, &peer_entry->ba_tx_done_map))
|
goto out;
|
wl_err("%s, %d, tx_addba failed, reason=%d, lut_index=%d, tid=%d, map=%lu\n",
|
__func__, __LINE__,
|
rbuf[0],
|
addba->lut_index,
|
tid,
|
peer_entry->ba_tx_done_map);
|
}
|
out:
|
kfree(rbuf);
|
}
|
|
void sprdwl_tx_send_addba(struct sprdwl_vif *vif, void *data, int len)
|
{
|
sprdwl_tx_ba_mgmt(vif->priv, data, len, WIFI_CMD_ADDBA_REQ,
|
vif->ctx_id);
|
}
|
|
void sprdwl_tx_send_delba(struct sprdwl_vif *vif, void *data, int len)
|
{
|
u8 i;
|
struct host_delba_param *delba;
|
|
delba = (struct host_delba_param *)data;
|
for (i = 0; i < SPRDWL_AC_MAX; i++)
|
sprdwl_tx_ba_mgmt(vif->priv, delba + i,
|
sizeof(struct host_delba_param),
|
WIFI_CMD_DELBA_REQ,
|
vif->ctx_id);
|
}
|
|
void sprdwl_tx_addba(struct sprdwl_intf *intf,
|
struct sprdwl_peer_entry *peer_entry, unsigned char tid)
|
{
|
#define WIN_SIZE 64
|
struct host_addba_param addba;
|
struct sprdwl_work *misc_work;
|
struct sprdwl_vif *vif;
|
|
vif = ctx_id_to_vif(intf->priv, peer_entry->ctx_id);
|
if (!vif)
|
return;
|
memset(&addba, 0x0, sizeof(struct host_addba_param));
|
|
addba.lut_index = peer_entry->lut_index;
|
ether_addr_copy(addba.perr_mac_addr, peer_entry->tx.da);
|
wl_debug("%s, lut_index is %d\n", __func__, peer_entry->lut_index);
|
addba.dialog_token = 1;
|
addba.addba_param.amsdu_permit = 0;
|
addba.addba_param.ba_policy = DOT11_ADDBA_POLICY_IMMEDIATE;
|
addba.addba_param.tid = tid;
|
addba.addba_param.buffer_size = WIN_SIZE;
|
misc_work = sprdwl_alloc_work(sizeof(struct host_addba_param));
|
if (!misc_work) {
|
wl_err("%s out of memory\n", __func__);
|
sprdwl_put_vif(vif);
|
return;
|
}
|
misc_work->vif = vif;
|
misc_work->id = SPRDWL_WORK_ADDBA;
|
memcpy(misc_work->data, &addba, sizeof(struct host_addba_param));
|
|
sprdwl_queue_work(vif->priv, misc_work);
|
sprdwl_put_vif(vif);
|
}
|
|
void sprdwl_tx_delba(struct sprdwl_intf *intf,
|
struct sprdwl_peer_entry *peer_entry, unsigned int ac_index)
|
{
|
struct host_delba_param delba[SPRDWL_AC_MAX];
|
struct sprdwl_work *misc_work;
|
struct sprdwl_vif *vif;
|
|
vif = ctx_id_to_vif(intf->priv, peer_entry->ctx_id);
|
if (!vif)
|
return;
|
memset(delba, 0x0, sizeof(delba));
|
|
wl_info("enter--at %s\n", __func__);
|
ether_addr_copy(delba[ac_index].perr_mac_addr, peer_entry->tx.da);
|
delba[ac_index].lut_index = peer_entry->lut_index;
|
delba[ac_index].delba_param.initiator = 1;
|
delba[ac_index].delba_param.tid = qos_index_2_tid(ac_index);
|
delba[ac_index].reason_code = 0;
|
|
|
misc_work =
|
sprdwl_alloc_work(sizeof(struct host_delba_param) * SPRDWL_AC_MAX);
|
if (!misc_work) {
|
wl_err("%s out of memory\n", __func__);
|
sprdwl_put_vif(vif);
|
return;
|
}
|
misc_work->vif = vif;
|
misc_work->id = SPRDWL_WORK_DELBA;
|
memcpy(misc_work->data, delba,
|
sizeof(struct host_delba_param) * SPRDWL_AC_MAX);
|
peer_entry->ht_enable = 0;
|
peer_entry->ba_tx_done_map = 0;
|
|
sprdwl_queue_work(vif->priv, misc_work);
|
sprdwl_put_vif(vif);
|
}
|
|
#ifdef CPUFREQ_UPDATE_SUPPORT
|
int sprdwl_notifier_boost(struct notifier_block *nb, unsigned long event, void *data)
|
{
|
#if KERNEL_VERSION(5, 4, 19) <= LINUX_VERSION_CODE
|
struct cpufreq_policy_data *policy = data;
|
#else
|
struct cpufreq_policy *policy = data;
|
#endif
|
unsigned long min_freq;
|
unsigned long max_freq = policy->cpuinfo.max_freq;
|
struct sprdwl_intf *intf = get_intf();
|
u8 boost;
|
|
if(NULL == intf)
|
return NOTIFY_DONE;
|
|
boost = intf->boost;
|
|
#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE
|
if (event != CPUFREQ_CREATE_POLICY)
|
#else
|
if (event != CPUFREQ_ADJUST)
|
#endif
|
return NOTIFY_DONE;
|
|
min_freq = boost ? 1200000 : 400000;
|
cpufreq_verify_within_limits(policy, min_freq, max_freq);
|
|
return NOTIFY_OK;
|
}
|
|
void sprdwl_boost(void)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
|
if (intf->boost == 0) {
|
intf->boost = 1;
|
cpufreq_update_policy(0);
|
}
|
}
|
|
void sprdwl_unboost(void)
|
{
|
struct sprdwl_intf *intf = get_intf();
|
|
if (intf->boost == 1) {
|
intf->boost = 0;
|
cpufreq_update_policy(0);
|
}
|
}
|
#endif /* CPUFREQ_UPDATE_SUPPORT */
|
|
void adjust_txnum_level(char *buf, unsigned char offset)
|
{
|
#define MAX_LEN 4
|
unsigned int cnt = 0;
|
unsigned int i = 0;
|
struct sprdwl_intf *intf = get_intf();
|
|
for (i = 0; i < MAX_LEN; (cnt *= 10), i++) {
|
if ((buf[offset + i] >= '0') &&
|
(buf[offset + i] <= '9')) {
|
cnt += (buf[offset + i] - '0');
|
} else {
|
cnt /= 10;
|
break;
|
}
|
}
|
|
if (cnt < 0 || cnt > 9999)
|
cnt = BOOST_TXNUM_LEVEL;
|
intf->txnum_level = cnt;
|
wl_info("credit_level: %d\n", intf->txnum_level);
|
#undef MAX_LEN
|
}
|
|
void adjust_rxnum_level(char *buf, unsigned char offset)
|
{
|
#define MAX_LEN 2
|
unsigned int cnt = 0;
|
unsigned int i = 0;
|
struct sprdwl_intf *intf = get_intf();
|
|
for (i = 0; i < MAX_LEN; (cnt *= 10), i++) {
|
if ((buf[offset + i] >= '0') &&
|
(buf[offset + i] <= '9')) {
|
cnt += (buf[offset + i] - '0');
|
} else {
|
cnt /= 10;
|
break;
|
}
|
}
|
|
if (cnt < 0 || cnt > 99)
|
cnt = BOOST_RXNUM_LEVEL;
|
intf->rxnum_level = cnt;
|
wl_info("rxnum_level: %d\n", intf->rxnum_level);
|
#undef MAX_LEN
|
}
|
|
void sprdwl_count_rx_tp_tcp_ack(struct sprdwl_intf *intf, u32 len)
|
{
|
unsigned long long timeus = 0;
|
struct sprdwl_rx_if *rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx;
|
|
rx_if->rx_total_len += (unsigned long)len;
|
if (rx_if->rx_total_len == (unsigned long)len) {
|
rx_if->rxtimebegin = ktime_get();
|
return;
|
}
|
|
rx_if->rxtimeend = ktime_get();
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
timeus = div_u64(rx_if->rxtimeend.tv64 - rx_if->rxtimebegin.tv64, NSEC_PER_USEC);
|
#else
|
timeus = div_u64(rx_if->rxtimeend - rx_if->rxtimebegin, NSEC_PER_USEC);
|
#endif
|
|
if(!timeus) {
|
return;
|
}
|
|
if (div_u64(((unsigned long long)rx_if->rx_total_len * 8 ) , (u32)timeus) >= (unsigned long long)intf->tcpack_delay_th_in_mb &&
|
timeus > (unsigned long long)intf->tcpack_time_in_ms * USEC_PER_MSEC) {
|
rx_if->rx_total_len = 0;
|
enable_tcp_ack_delay("tcpack_delay_en=1", strlen("tcpack_delay_en="));
|
} else if (div_u64((rx_if->rx_total_len * 8 ) , (u32)timeus) < intf->tcpack_delay_th_in_mb &&
|
timeus > intf->tcpack_time_in_ms * USEC_PER_MSEC) {
|
rx_if->rx_total_len = 0;
|
enable_tcp_ack_delay("tcpack_delay_en=0", strlen("tcpack_delay_en="));
|
}
|
}
|
|
int sprdwl_bus_init(struct sprdwl_priv *priv)
|
{
|
int ret = -EINVAL, chn = 0;
|
|
if (priv->hw_type == SPRDWL_HW_SDIO) {
|
g_intf_ops.hif_ops = sdio_hif_ops;
|
g_intf_ops.max_num =
|
sizeof(sdio_hif_ops)/sizeof(struct mchn_ops_t);
|
} else if (priv->hw_type == SPRDWL_HW_PCIE) {
|
g_intf_ops.hif_ops = pcie_hif_ops;
|
g_intf_ops.max_num =
|
sizeof(pcie_hif_ops)/sizeof(struct mchn_ops_t);
|
} else if (priv->hw_type == SPRDWL_HW_USB) {
|
g_intf_ops.hif_ops = usb_hif_ops;
|
g_intf_ops.max_num =
|
sizeof(usb_hif_ops)/sizeof(struct mchn_ops_t);
|
}
|
|
if (g_intf_ops.max_num < MAX_CHN_NUM) {
|
wl_info("%s: register %d ops\n",
|
__func__, g_intf_ops.max_num);
|
|
for (chn = 0; chn < g_intf_ops.max_num; chn++) {
|
ret = sprdwcn_bus_chn_init(&g_intf_ops.hif_ops[chn]);
|
if (ret < 0)
|
goto err;
|
}
|
return 0;
|
}
|
err:
|
wl_err("%s: unregister %d ops\n",
|
__func__, g_intf_ops.max_num);
|
|
for (; chn > 0; chn--)
|
sprdwcn_bus_chn_deinit(&g_intf_ops.hif_ops[chn]);
|
|
g_intf_ops.hif_ops = NULL;
|
g_intf_ops.max_num = 0;
|
|
return ret;
|
}
|
|
void sprdwl_bus_deinit(void)
|
{
|
int chn = 0;
|
|
for (chn = 0; chn < g_intf_ops.max_num; chn++)
|
sprdwcn_bus_chn_deinit(&g_intf_ops.hif_ops[chn]);
|
}
|
|
int sprdwl_intf_init(struct sprdwl_priv *priv, struct sprdwl_intf *intf)
|
{
|
int ret = -EINVAL;
|
|
ret = sprdwl_bus_init(priv);
|
if (ret < 0)
|
return ret;
|
|
g_intf_ops.intf = (void *)intf;
|
/* TODO: Need we reserve g_intf_ops? */
|
intf->hw_intf = (void *)&g_intf_ops;
|
|
priv->hw_priv = intf;
|
priv->hw_offset = intf->hif_offset;
|
intf->priv = priv;
|
intf->fw_awake = 1;
|
intf->fw_power_down = 0;
|
intf->txnum_level = BOOST_TXNUM_LEVEL;
|
intf->rxnum_level = BOOST_RXNUM_LEVEL;
|
|
#ifdef CPUFREQ_UPDATE_SUPPORT
|
intf->boost = 0;
|
#endif /* CPUFREQ_UPDATE_SUPPORT */
|
|
intf->tcpack_time_in_ms = RX_TP_COUNT_IN_MS;
|
intf->tcpack_delay_th_in_mb = DROPACK_TP_TH_IN_M;
|
|
init_completion(&intf->suspend_completed);
|
|
return ret;
|
}
|
|
void sprdwl_intf_deinit(struct sprdwl_intf *dev)
|
{
|
g_intf_ops.intf = NULL;
|
g_intf_ops.max_num = 0;
|
dev->hw_intf = NULL;
|
}
|
|
int sprdwl_dis_flush_txlist(struct sprdwl_intf *intf, u8 lut_index)
|
{
|
struct sprdwl_tx_msg *tx_msg;
|
int i, j;
|
|
if (lut_index <= 5) {
|
wl_err("err lut_index:%d, %s, %d\n",
|
lut_index, __func__, __LINE__);
|
return -1;
|
}
|
wl_debug("disconnect, flush qoslist, %s, %d\n", __func__, __LINE__);
|
tx_msg = (struct sprdwl_tx_msg *)intf->sprdwl_tx;
|
for (i = 0; i < SPRDWL_MODE_MAX; i++)
|
for (j = 0; j < SPRDWL_AC_MAX; j++)
|
sprdwl_flush_tx_qoslist(tx_msg, i, j, lut_index);
|
return 0;
|
}
|