2069 lines
55 KiB
C
2069 lines
55 KiB
C
|
/*
|
||
|
* Data Transmission thread implementation for XRadio drivers
|
||
|
*
|
||
|
* Copyright (c) 2013
|
||
|
* Xradio Technology Co., Ltd. <www.xradiotech.com>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 as
|
||
|
* published by the Free Software Foundation.
|
||
|
*/
|
||
|
|
||
|
#include <net/mac80211.h>
|
||
|
#include <linux/kthread.h>
|
||
|
|
||
|
#include "xradio.h"
|
||
|
#include "bh.h"
|
||
|
#include "hwio.h"
|
||
|
#include "wsm.h"
|
||
|
#include "sbus.h"
|
||
|
|
||
|
#ifdef SUPPORT_FW_DBG_INF
|
||
|
#include "fw_dbg_inf.h"
|
||
|
#endif
|
||
|
|
||
|
/* TODO: Verify these numbers with WSM specification. */
|
||
|
#define DOWNLOAD_BLOCK_SIZE_WR (0x1000 - 4)
|
||
|
/* an SPI message cannot be bigger than (2"12-1)*2 bytes
|
||
|
* "*2" to cvt to bytes */
|
||
|
#define MAX_SZ_RD_WR_BUFFERS (DOWNLOAD_BLOCK_SIZE_WR*2)
|
||
|
#define PIGGYBACK_CTRL_REG (2)
|
||
|
#define EFFECTIVE_BUF_SIZE (MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
|
||
|
|
||
|
#define DEV_WAKEUP_MAX_TIME (HZ<<1) /* =HZ*2 = 2s*/
|
||
|
#define DEV_WAKEUP_WAIT_TIME (HZ/50) /*=20ms*/
|
||
|
#define BH_TX_BURST_NONTXOP (16)
|
||
|
|
||
|
#if (SDIO_BLOCK_SIZE > 500)
|
||
|
#define SKB_CACHE_LEN (SDIO_BLOCK_SIZE)
|
||
|
#elif (SDIO_BLOCK_SIZE > 250)
|
||
|
#define SKB_CACHE_LEN (SDIO_BLOCK_SIZE<<1)
|
||
|
#else
|
||
|
#define SKB_CACHE_LEN xr_sdio_blksize_align(500)
|
||
|
#endif
|
||
|
#define SKB_RESV_MAX (1900)
|
||
|
|
||
|
int tx_burst_limit = BH_TX_BURST_NONTXOP;
|
||
|
|
||
|
/* Suspend state privates */
|
||
|
enum xradio_bh_pm_state {
|
||
|
XRADIO_BH_RESUMED = 0,
|
||
|
XRADIO_BH_SUSPEND,
|
||
|
XRADIO_BH_SUSPENDED,
|
||
|
XRADIO_BH_RESUME,
|
||
|
};
|
||
|
typedef int (*xradio_wsm_handler) (struct xradio_common *hw_priv, u8 *data,
|
||
|
size_t size);
|
||
|
|
||
|
static inline u32 bh_time_interval(struct timeval *oldtime)
|
||
|
{
|
||
|
u32 time_int;
|
||
|
struct timeval newtime;
|
||
|
do_gettimeofday(&newtime);
|
||
|
time_int = (newtime.tv_sec - oldtime->tv_sec) * 1000000 + \
|
||
|
(long)(newtime.tv_usec - oldtime->tv_usec);
|
||
|
return time_int;
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifdef MCAST_FWDING
|
||
|
int wsm_release_buffer_to_fw(struct xradio_vif *priv, int count);
|
||
|
#endif
|
||
|
static int xradio_bh(void *arg);
|
||
|
static void xradio_put_skb(struct xradio_common *hw_priv, struct sk_buff *skb);
|
||
|
static struct sk_buff *xradio_get_skb(struct xradio_common *hw_priv, size_t len, u8 *flags);
|
||
|
static inline int xradio_put_resv_skb(struct xradio_common *hw_priv,
|
||
|
struct sk_buff *skb, u8 flags);
|
||
|
|
||
|
#ifdef BH_PROC_THREAD
|
||
|
static int xradio_proc(void *arg);
|
||
|
int bh_proc_init(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
int i;
|
||
|
struct bh_items *pool = NULL;
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
memset(&hw_priv->proc, 0, sizeof(struct bh_proc));
|
||
|
/* init locks and wait_queue */
|
||
|
spin_lock_init(&hw_priv->proc.lock);
|
||
|
init_waitqueue_head(&hw_priv->proc.proc_wq);
|
||
|
|
||
|
/* init pool and txrx queues */
|
||
|
atomic_set(&hw_priv->proc.proc_tx, 0);
|
||
|
atomic_set(&hw_priv->proc.rx_queued, 0);
|
||
|
atomic_set(&hw_priv->proc.tx_queued, 0);
|
||
|
INIT_LIST_HEAD(&hw_priv->proc.bh_tx);
|
||
|
INIT_LIST_HEAD(&hw_priv->proc.bh_rx);
|
||
|
INIT_LIST_HEAD(&hw_priv->proc.bh_free);
|
||
|
pool = xr_kzalloc(PROC_POOL_SIZE, false);
|
||
|
if (!pool)
|
||
|
return -ENOMEM;
|
||
|
else
|
||
|
hw_priv->proc.bh_pool[0] = pool;
|
||
|
for (i = 0; i < ITEM_RESERVED; ++i)
|
||
|
list_add_tail(&pool[i].head, &hw_priv->proc.bh_free);
|
||
|
|
||
|
/* init proc thread.*/
|
||
|
hw_priv->proc.proc_state = 0;
|
||
|
hw_priv->proc.proc_thread =
|
||
|
kthread_create(&xradio_proc, hw_priv, XRADIO_PROC_THREAD);
|
||
|
if (IS_ERR(hw_priv->proc.proc_thread)) {
|
||
|
ret = PTR_ERR(hw_priv->proc.proc_thread);
|
||
|
hw_priv->proc.proc_thread = NULL;
|
||
|
} else {
|
||
|
#ifdef HAS_PUT_TASK_STRUCT
|
||
|
get_task_struct(hw_priv->proc.proc_thread);
|
||
|
#endif
|
||
|
wake_up_process(hw_priv->proc.proc_thread);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int bh_proc_flush_txqueue(struct xradio_common *hw_priv, int if_id)
|
||
|
{
|
||
|
struct bh_items *item = NULL, *tmp = NULL;
|
||
|
spin_lock(&hw_priv->proc.lock);
|
||
|
/*flush proc tx queue, no need to dev_kfree_skb */
|
||
|
list_for_each_entry_safe(item, tmp, &hw_priv->proc.bh_tx, head) {
|
||
|
if (item) {
|
||
|
if (XRWL_ALL_IFS == if_id || item->if_id == if_id) {
|
||
|
item->data = NULL;
|
||
|
list_move_tail(&item->head, &hw_priv->proc.bh_free);
|
||
|
atomic_sub(1, &hw_priv->proc.tx_queued);
|
||
|
}
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s tx item is NULL!\n", __func__);
|
||
|
}
|
||
|
}
|
||
|
if (XRWL_ALL_IFS == if_id) {
|
||
|
INIT_LIST_HEAD(&hw_priv->proc.bh_tx);
|
||
|
atomic_set(&hw_priv->proc.tx_queued, 0);
|
||
|
atomic_set(&hw_priv->proc.proc_tx, 0);
|
||
|
}
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
return 0;
|
||
|
}
|
||
|
int bh_proc_flush_queue(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
struct bh_items *item = NULL;
|
||
|
spin_lock(&hw_priv->proc.lock);
|
||
|
/*flush proc rx queue */
|
||
|
while (!list_empty(&hw_priv->proc.bh_rx)) {
|
||
|
item = list_first_entry(&hw_priv->proc.bh_rx,
|
||
|
struct bh_items, head);
|
||
|
if (item) {
|
||
|
if (item->data) {
|
||
|
dev_kfree_skb((struct sk_buff *)item->data);
|
||
|
item->data = NULL;
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s item->data is NULL!\n", __func__);
|
||
|
}
|
||
|
list_move_tail(&item->head, &hw_priv->proc.bh_free);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s rx item is NULL!\n", __func__);
|
||
|
}
|
||
|
}
|
||
|
INIT_LIST_HEAD(&hw_priv->proc.bh_rx);
|
||
|
atomic_set(&hw_priv->proc.rx_queued, 0);
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
|
||
|
/*flush proc tx queue, no need to dev_kfree_skb */
|
||
|
bh_proc_flush_txqueue(hw_priv, XRWL_ALL_IFS);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void bh_proc_deinit(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
struct task_struct *thread = hw_priv->proc.proc_thread;
|
||
|
int i = 0;
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
/* deinit proc thread */
|
||
|
if (thread) {
|
||
|
hw_priv->proc.proc_thread = NULL;
|
||
|
kthread_stop(thread);
|
||
|
#ifdef HAS_PUT_TASK_STRUCT
|
||
|
put_task_struct(thread);
|
||
|
#endif
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"%s thread is NULL!\n", __func__);
|
||
|
}
|
||
|
|
||
|
/* clear tx/rx queue */
|
||
|
bh_proc_flush_queue(hw_priv);
|
||
|
|
||
|
/* clear free queue */
|
||
|
INIT_LIST_HEAD(&hw_priv->proc.bh_free);
|
||
|
|
||
|
/*free proc pool*/
|
||
|
for (i = 0; i < PROC_POOL_NUM; i++) {
|
||
|
if (hw_priv->proc.bh_pool[i]) {
|
||
|
kfree(hw_priv->proc.bh_pool[i]);
|
||
|
hw_priv->proc.bh_pool[i] = NULL;
|
||
|
} else if (i == 0) {
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"%s bh_pool[0] is NULL!\n", __func__);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
int bh_proc_reinit(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
bh_proc_deinit(hw_priv);
|
||
|
return bh_proc_init(hw_priv);
|
||
|
}
|
||
|
|
||
|
static struct bh_items *xradio_get_free_item(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
struct bh_items *item = NULL;
|
||
|
if (likely(!list_empty(&hw_priv->proc.bh_free))) {
|
||
|
item = list_first_entry(&hw_priv->proc.bh_free,
|
||
|
struct bh_items, head);
|
||
|
} else {
|
||
|
int i = 0;
|
||
|
struct bh_items *pool = NULL;
|
||
|
for (i = 0; i < PROC_POOL_NUM; i++) {
|
||
|
if (!hw_priv->proc.bh_pool[i]) {
|
||
|
pool = xr_kzalloc(PROC_POOL_SIZE, false);
|
||
|
hw_priv->proc.bh_pool[i] = pool;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (pool) {
|
||
|
bh_printk(XRADIO_DBG_WARN, "%s alloc pool%d!\n",
|
||
|
__func__, i);
|
||
|
for (i = 0; i < ITEM_RESERVED; ++i)
|
||
|
list_add_tail(&pool[i].head, &hw_priv->proc.bh_free);
|
||
|
item = list_first_entry(&hw_priv->proc.bh_free,
|
||
|
struct bh_items, head);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s Failed alloc pool%d!\n",
|
||
|
__func__, i);
|
||
|
}
|
||
|
}
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
void xradio_proc_wakeup(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
#if BH_PROC_TX
|
||
|
if (atomic_add_return(1, &hw_priv->proc.proc_tx) == 1) {
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s\n", __func__);
|
||
|
wake_up(&hw_priv->proc.proc_wq);
|
||
|
}
|
||
|
#else
|
||
|
xradio_bh_wakeup(hw_priv);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#if PERF_INFO_TEST
|
||
|
struct timeval proc_start_time;
|
||
|
#endif
|
||
|
|
||
|
#if BH_PROC_DPA
|
||
|
#define PROC_HIGH_IDX 0
|
||
|
#define PROC_LOW_IDX 4
|
||
|
const struct thread_dpa g_dpa[] = {
|
||
|
{SCHED_FIFO, 25},
|
||
|
{SCHED_FIFO, 50},
|
||
|
{SCHED_FIFO, 75},
|
||
|
{SCHED_FIFO, 99},
|
||
|
{SCHED_NORMAL, 0}
|
||
|
};
|
||
|
int thread_dpa_up(struct task_struct *p, s8 *prio_index)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
s8 idx = 0;
|
||
|
if (unlikely(!p || !prio_index)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s, task_struct=%p, prio_index=%p\n",
|
||
|
__func__, p, prio_index);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
idx = (*prio_index) - 1;
|
||
|
if (idx > PROC_HIGH_IDX) {
|
||
|
struct sched_param param = {
|
||
|
.sched_priority = g_dpa[idx].priority
|
||
|
};
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s=%d\n", __func__, idx);
|
||
|
ret = sched_setscheduler(p, g_dpa[idx].policy, ¶m);
|
||
|
if (!ret)
|
||
|
*prio_index = idx;
|
||
|
else
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s, sched_setscheduler failed, idx=%d\n",
|
||
|
__func__, idx);
|
||
|
return ret;
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s, prio_index=%d\n",
|
||
|
__func__, idx + 1);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
int thread_dpa_down(struct task_struct *p, u8 *prio_index)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
s8 idx = 0;
|
||
|
if (unlikely(!p || !prio_index)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s, task_struct=%p, prio_index=%p\n",
|
||
|
__func__, p, prio_index);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
idx = (*prio_index) + 1;
|
||
|
if (idx < PROC_LOW_IDX) {
|
||
|
struct sched_param param = {
|
||
|
.sched_priority = g_dpa[idx].priority
|
||
|
};
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s=%d\n", __func__, idx);
|
||
|
ret = sched_setscheduler(p, g_dpa[idx].policy, ¶m);
|
||
|
if (!ret)
|
||
|
*prio_index = idx;
|
||
|
else
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s, sched_setscheduler failed, idx=%d\n",
|
||
|
__func__, idx);
|
||
|
return ret;
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s, prio_index=%d\n",
|
||
|
__func__, idx - 1);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
static inline int proc_set_priority(struct xradio_common *hw_priv, u8 idx)
|
||
|
{
|
||
|
struct sched_param param = {
|
||
|
.sched_priority = g_dpa[idx].priority
|
||
|
};
|
||
|
hw_priv->proc.proc_prio = idx;
|
||
|
return sched_setscheduler(hw_priv->proc.proc_thread,
|
||
|
g_dpa[idx].policy, ¶m);
|
||
|
}
|
||
|
int dpa_proc_tx;
|
||
|
int dpa_proc_rx;
|
||
|
u32 proc_dpa_cnt;
|
||
|
u32 proc_up_cnt;
|
||
|
u32 proc_down_cnt;
|
||
|
static inline int proc_dpa_update(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
int tx_ret = 0;
|
||
|
int rx_ret = 0;
|
||
|
int dpa_old = 0;
|
||
|
int i = 0;
|
||
|
|
||
|
if (!hw_priv->proc.proc_thread)
|
||
|
return -ENOENT;
|
||
|
++proc_dpa_cnt;
|
||
|
/*update by rx.*/
|
||
|
dpa_old = dpa_proc_rx;
|
||
|
dpa_proc_rx = atomic_read(&hw_priv->proc.rx_queued);
|
||
|
if (dpa_proc_rx >= (ITEM_RESERVED>>2) ||
|
||
|
dpa_proc_rx >= (dpa_old + 10)) {
|
||
|
rx_ret = 1;
|
||
|
} else if ((dpa_proc_rx + 20) < dpa_old ||
|
||
|
dpa_proc_rx < (ITEM_RESERVED>>5)) {
|
||
|
rx_ret = -1;
|
||
|
}
|
||
|
|
||
|
/* update by tx.*/
|
||
|
dpa_old = dpa_proc_tx;
|
||
|
for (dpa_proc_tx = 0, i = 0; i < 4; ++i) {
|
||
|
dpa_proc_tx += hw_priv->tx_queue[i].num_queued -
|
||
|
hw_priv->tx_queue[i].num_pending;
|
||
|
}
|
||
|
if (dpa_proc_tx > (dpa_old + 10) ||
|
||
|
dpa_proc_tx > XRWL_HOST_VIF0_11N_THROTTLE) {
|
||
|
tx_ret = 1;
|
||
|
} else if ((dpa_proc_tx + 10) < dpa_old ||
|
||
|
dpa_proc_tx < (XRWL_HOST_VIF0_11N_THROTTLE>>2)) {
|
||
|
tx_ret = -1;
|
||
|
}
|
||
|
|
||
|
if (rx_ret > 0 || tx_ret > 0) {
|
||
|
++proc_up_cnt;
|
||
|
if (++hw_priv->proc.prio_cnt > 10) {
|
||
|
hw_priv->proc.prio_cnt = 0;
|
||
|
return thread_dpa_up(hw_priv->proc.proc_thread,
|
||
|
&hw_priv->proc.proc_prio);
|
||
|
}
|
||
|
} else if (rx_ret < 0 && tx_ret < 0) {
|
||
|
++proc_down_cnt;
|
||
|
if (--hw_priv->proc.prio_cnt < -10) {
|
||
|
hw_priv->proc.prio_cnt = 0;
|
||
|
return thread_dpa_down(hw_priv->proc.proc_thread,
|
||
|
&hw_priv->proc.proc_prio);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static inline int xradio_proc(void *arg)
|
||
|
{
|
||
|
struct xradio_common *hw_priv = arg;
|
||
|
#if !BH_PROC_DPA
|
||
|
struct sched_param param = {
|
||
|
.sched_priority = 99
|
||
|
};
|
||
|
#endif
|
||
|
int ret = 0;
|
||
|
int term = 0;
|
||
|
int tx = 0;
|
||
|
int rx = 0;
|
||
|
#if BH_PROC_DPA
|
||
|
int dpa_num = 0;
|
||
|
#endif
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
#if BH_PROC_DPA
|
||
|
ret = proc_set_priority(hw_priv, 3);
|
||
|
#else
|
||
|
ret = sched_setscheduler(hw_priv->proc.proc_thread,
|
||
|
SCHED_FIFO, ¶m);
|
||
|
#endif
|
||
|
if (ret)
|
||
|
bh_printk(XRADIO_DBG_WARN, "%s sched_setscheduler failed(%d)\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
for (;;) {
|
||
|
PERF_INFO_GETTIME(&proc_start_time);
|
||
|
ret = wait_event_interruptible(hw_priv->proc.proc_wq, ({
|
||
|
term = kthread_should_stop();
|
||
|
#if BH_PROC_RX
|
||
|
rx = atomic_read(&hw_priv->proc.rx_queued);
|
||
|
#else
|
||
|
rx = 0;
|
||
|
#endif
|
||
|
#if BH_PROC_TX
|
||
|
tx = atomic_xchg(&hw_priv->proc.proc_tx, 0);
|
||
|
#else
|
||
|
tx = 0;
|
||
|
#endif
|
||
|
(term || ((rx || tx) &&
|
||
|
!hw_priv->bh_error && !hw_priv->proc.proc_state &&
|
||
|
XRADIO_BH_RESUMED == atomic_read(&hw_priv->bh_suspend))); }));
|
||
|
|
||
|
/* 0--proc is going to be shut down */
|
||
|
if (term) {
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s exit!\n", __func__);
|
||
|
break;
|
||
|
} else if (ret < 0) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s wait_event err=%d!\n",
|
||
|
__func__, ret);
|
||
|
continue; /*continue to wait for exit */
|
||
|
}
|
||
|
PERF_INFO_STAMP_UPDATE(&proc_start_time, &proc_wait, 0);
|
||
|
|
||
|
while (rx || tx) {
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s rx=%d, tx=%d\n",
|
||
|
__func__, rx, tx);
|
||
|
#if BH_PROC_RX
|
||
|
/* 1--handle rx*/
|
||
|
if (rx) {
|
||
|
size_t rx_len = 0;
|
||
|
spin_lock(&hw_priv->proc.lock);
|
||
|
if (likely(!list_empty(&hw_priv->proc.bh_rx))) {
|
||
|
struct bh_items *rx_item = NULL;
|
||
|
struct sk_buff *rx_skb = NULL;
|
||
|
u8 flags = 0;
|
||
|
rx_item = list_first_entry(&hw_priv->proc.bh_rx,
|
||
|
struct bh_items, head);
|
||
|
if (rx_item) {
|
||
|
rx_skb = (struct sk_buff *)rx_item->data;
|
||
|
flags = rx_item->flags;
|
||
|
rx_item->data = NULL;
|
||
|
rx_len = rx_item->datalen;
|
||
|
list_move_tail(&rx_item->head,
|
||
|
&hw_priv->proc.bh_free);
|
||
|
}
|
||
|
rx = atomic_sub_return(1, &hw_priv->proc.rx_queued);
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
if (rx_skb) {
|
||
|
ret = wsm_handle_rx(hw_priv, rx_item->flags, &rx_skb);
|
||
|
/* Reclaim the SKB buffer */
|
||
|
if (rx_skb) {
|
||
|
if (xradio_put_resv_skb(hw_priv, rx_skb, rx_item->flags))
|
||
|
xradio_put_skb(hw_priv, rx_skb);
|
||
|
rx_skb = NULL;
|
||
|
}
|
||
|
if (ret) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"wsm_handle_rx err=%d!\n", ret);
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s rx_item data is NULL\n", __func__);
|
||
|
}
|
||
|
hw_priv->proc.rxed_num++;
|
||
|
} else {
|
||
|
rx = 0;
|
||
|
hw_priv->proc.proc_state = 1; /*need to restart proc*/
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"rx_queued=%d, but proc.bh_rx is empty!\n",
|
||
|
atomic_read(&hw_priv->proc.rx_queued));
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
}
|
||
|
PERF_INFO_STAMP_UPDATE(&proc_start_time, &proc_rx, rx_len);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if BH_PROC_TX
|
||
|
/* 2--handle tx*/
|
||
|
if (tx) {
|
||
|
u8 *data = NULL;
|
||
|
size_t tx_len = 0;
|
||
|
int burst = 0;
|
||
|
int vif_selected = 0;
|
||
|
ret = wsm_get_tx(hw_priv, &data, &tx_len,
|
||
|
&burst, &vif_selected);
|
||
|
if (ret < 0) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"wsm_get_tx err=%d!\n", ret);
|
||
|
tx = 0;
|
||
|
break;
|
||
|
} else if (ret) {
|
||
|
struct bh_items *item = NULL;
|
||
|
spin_lock(&hw_priv->proc.lock);
|
||
|
item = xradio_get_free_item(hw_priv);
|
||
|
if (likely(item)) {
|
||
|
SYS_BUG(item->data);
|
||
|
item->data = data;
|
||
|
item->datalen = tx_len;
|
||
|
if (unlikely(item->datalen != tx_len)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s datalen=%u, tx_len=%zu.\n",
|
||
|
__func__, item->datalen, tx_len);
|
||
|
}
|
||
|
item->if_id = vif_selected;
|
||
|
item->flags = 0;
|
||
|
list_move_tail(&item->head, &hw_priv->proc.bh_tx);
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
if (atomic_add_return(1, &hw_priv->proc.tx_queued) == 1 &&
|
||
|
hw_priv->bh_thread) {
|
||
|
xradio_bh_wakeup(hw_priv);
|
||
|
}
|
||
|
hw_priv->proc.txed_num++;
|
||
|
bh_printk(XRADIO_DBG_NIY,
|
||
|
"%s Tx if=%d, datalen=%zu, queued=%d\n",
|
||
|
__func__, vif_selected, tx_len,
|
||
|
atomic_read(&hw_priv->proc.tx_queued));
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s pool is empty\n", __func__);
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
hw_priv->proc.proc_state = 1; /*need to restart proc*/
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
tx = 0;
|
||
|
bh_printk(XRADIO_DBG_NIY, "wsm_get_tx no data!\n");
|
||
|
}
|
||
|
PERF_INFO_STAMP_UPDATE(&proc_start_time, &proc_tx, tx_len);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if BH_PROC_DPA
|
||
|
if (++dpa_num > 20) {
|
||
|
proc_dpa_update(hw_priv);
|
||
|
dpa_num = 0;
|
||
|
}
|
||
|
#endif
|
||
|
} /* while */
|
||
|
|
||
|
if (hw_priv->proc.proc_state) {
|
||
|
/* proc error occurs, to restart driver.*/
|
||
|
hw_priv->bh_error = 1;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
/* for debug */
|
||
|
if (!atomic_read(&hw_priv->proc.proc_tx)) {
|
||
|
int num = 0;
|
||
|
int pending = 0;
|
||
|
int i = 0;
|
||
|
for (i = 0; i < 4; ++i) {
|
||
|
pending += hw_priv->tx_queue[i].num_pending;
|
||
|
num += hw_priv->tx_queue[i].num_queued -
|
||
|
hw_priv->tx_queue[i].num_pending;
|
||
|
}
|
||
|
if (num && !atomic_read(&hw_priv->proc.proc_tx)) {
|
||
|
bh_printk(XRADIO_DBG_NIY,
|
||
|
"%s rx=%d, tx=%d, num=%d, pending=%d,"
|
||
|
" rx_queued=%d, bufuse=%d\n",
|
||
|
__func__, rx, tx, num, pending,
|
||
|
atomic_read(&hw_priv->proc.rx_queued),
|
||
|
hw_priv->hw_bufs_used);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
} /* for (;;) */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#if BH_PROC_TX
|
||
|
static inline int xradio_bh_get(struct xradio_common *hw_priv, u8 **data,
|
||
|
size_t *tx_len, int *burst, int *vif_selected)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
/* check cmd first */
|
||
|
spin_lock(&hw_priv->wsm_cmd.lock);
|
||
|
if (hw_priv->wsm_cmd.ptr) {
|
||
|
*data = hw_priv->wsm_cmd.ptr;
|
||
|
*tx_len = hw_priv->wsm_cmd.len;
|
||
|
*burst = atomic_read(&hw_priv->proc.tx_queued) + 1;
|
||
|
*vif_selected = -1;
|
||
|
spin_unlock(&hw_priv->wsm_cmd.lock);
|
||
|
return 1;
|
||
|
}
|
||
|
spin_unlock(&hw_priv->wsm_cmd.lock);
|
||
|
|
||
|
/* check tx data */
|
||
|
spin_lock(&hw_priv->proc.lock);
|
||
|
if (!list_empty(&hw_priv->proc.bh_tx) &&
|
||
|
!atomic_read(&hw_priv->tx_lock) &&
|
||
|
hw_priv->hw_bufs_used < hw_priv->wsm_caps.numInpChBufs) {
|
||
|
struct bh_items *item = list_first_entry(
|
||
|
&hw_priv->proc.bh_tx, struct bh_items, head);
|
||
|
if (item && item->data) {
|
||
|
struct xradio_queue_item *queue_item =
|
||
|
(struct xradio_queue_item *)item->data;
|
||
|
queue_item->xmit_timestamp = jiffies;
|
||
|
queue_item->xmit_to_fw = 1;
|
||
|
*data = queue_item->skb->data;
|
||
|
*tx_len = item->datalen;
|
||
|
*vif_selected = item->if_id;
|
||
|
*burst = atomic_sub_return(1, &hw_priv->proc.tx_queued) + 1;
|
||
|
item->data = NULL;
|
||
|
list_move_tail(&item->head, &hw_priv->proc.bh_free);
|
||
|
ret = 1;
|
||
|
bh_printk(XRADIO_DBG_NIY, "%s tx_len=%zu, burst=%d!\n",
|
||
|
__func__, *tx_len, *burst);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s item=%p, data=%p!\n",
|
||
|
__func__, item, item->data);
|
||
|
ret = -ENOENT;
|
||
|
}
|
||
|
}
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if PERF_INFO_TEST
|
||
|
struct timeval bh_put_time;
|
||
|
#endif
|
||
|
|
||
|
static inline int xradio_bh_put(struct xradio_common *hw_priv,
|
||
|
struct sk_buff **skb_p, u8 flags)
|
||
|
{
|
||
|
struct bh_items *item = NULL;
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
PERF_INFO_GETTIME(&bh_put_time);
|
||
|
spin_lock(&hw_priv->proc.lock);
|
||
|
if (unlikely(!hw_priv->proc.proc_thread)) {
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"%s proc_thread is stopped!\n", __func__);
|
||
|
dev_kfree_skb(*skb_p);
|
||
|
*skb_p = NULL;
|
||
|
return 0;
|
||
|
}
|
||
|
item = xradio_get_free_item(hw_priv);
|
||
|
if (likely(item)) {
|
||
|
SYS_BUG(item->data);
|
||
|
item->data = (u8 *)(*skb_p);
|
||
|
item->datalen = (*skb_p)->len;
|
||
|
if (unlikely(item->datalen != (*skb_p)->len)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s datalen=%u, skblen=%u.\n",
|
||
|
__func__, item->datalen, (*skb_p)->len);
|
||
|
}
|
||
|
item->flags = flags;
|
||
|
if ((flags & ITEM_F_CMDCFM))
|
||
|
list_move(&item->head, &hw_priv->proc.bh_rx);
|
||
|
else
|
||
|
list_move_tail(&item->head, &hw_priv->proc.bh_rx);
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
PERF_INFO_STAMP_UPDATE(&bh_put_time, &get_item, 0);
|
||
|
if (atomic_add_return(1, &hw_priv->proc.rx_queued) == 1) {
|
||
|
wake_up(&hw_priv->proc.proc_wq);
|
||
|
}
|
||
|
*skb_p = NULL;
|
||
|
PERF_INFO_STAMP(&bh_put_time, &wake_proc, 0);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"%s pool is empty!\n", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
err:
|
||
|
spin_unlock(&hw_priv->proc.lock);
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
#endif /* #ifdef BH_PROC_THREAD */
|
||
|
|
||
|
int xradio_register_bh(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
int err = 0;
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
SYS_BUG(hw_priv->bh_thread);
|
||
|
atomic_set(&hw_priv->bh_rx, 0);
|
||
|
atomic_set(&hw_priv->bh_tx, 0);
|
||
|
atomic_set(&hw_priv->bh_term, 0);
|
||
|
atomic_set(&hw_priv->bh_suspend, XRADIO_BH_RESUMED);
|
||
|
hw_priv->buf_id_tx = 0;
|
||
|
hw_priv->buf_id_rx = 0;
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
sema_init(&hw_priv->bh_sem, 0);
|
||
|
atomic_set(&hw_priv->bh_wk, 0);
|
||
|
#else
|
||
|
init_waitqueue_head(&hw_priv->bh_wq);
|
||
|
#endif
|
||
|
init_waitqueue_head(&hw_priv->bh_evt_wq);
|
||
|
|
||
|
hw_priv->bh_thread = kthread_create(&xradio_bh, hw_priv, XRADIO_BH_THREAD);
|
||
|
if (IS_ERR(hw_priv->bh_thread)) {
|
||
|
err = PTR_ERR(hw_priv->bh_thread);
|
||
|
hw_priv->bh_thread = NULL;
|
||
|
} else {
|
||
|
#ifdef HAS_PUT_TASK_STRUCT
|
||
|
get_task_struct(hw_priv->bh_thread);
|
||
|
#endif
|
||
|
wake_up_process(hw_priv->bh_thread);
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
void xradio_unregister_bh(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
struct task_struct *thread = hw_priv->bh_thread;
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
if (SYS_WARN(!thread))
|
||
|
return;
|
||
|
|
||
|
hw_priv->bh_thread = NULL;
|
||
|
kthread_stop(thread);
|
||
|
#ifdef HAS_PUT_TASK_STRUCT
|
||
|
put_task_struct(thread);
|
||
|
#endif
|
||
|
bh_printk(XRADIO_DBG_NIY, "Unregister success.\n");
|
||
|
}
|
||
|
|
||
|
void xradio_irq_handler(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
DBG_INT_ADD(irq_count);
|
||
|
if (/* SYS_WARN */(hw_priv->bh_error))
|
||
|
return;
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
atomic_add(1, &hw_priv->bh_rx);
|
||
|
if (atomic_add_return(1, &hw_priv->bh_wk) == 1) {
|
||
|
up(&hw_priv->bh_sem);
|
||
|
}
|
||
|
#else
|
||
|
if (atomic_add_return(1, &hw_priv->bh_rx) == 1) {
|
||
|
wake_up(&hw_priv->bh_wq);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
void xradio_bh_wakeup(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
if (hw_priv->bh_error) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s bh_error=%d\n",
|
||
|
__func__, hw_priv->bh_error);
|
||
|
return;
|
||
|
}
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
atomic_add(1, &hw_priv->bh_tx);
|
||
|
if (atomic_add_return(1, &hw_priv->bh_wk) == 1) {
|
||
|
up(&hw_priv->bh_sem);
|
||
|
}
|
||
|
#else
|
||
|
if (atomic_add_return(1, &hw_priv->bh_tx) == 1) {
|
||
|
wake_up(&hw_priv->bh_wq);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
int xradio_bh_suspend(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
|
||
|
#ifdef MCAST_FWDING
|
||
|
int i = 0;
|
||
|
struct xradio_vif *priv = NULL;
|
||
|
#endif
|
||
|
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
if (hw_priv->bh_thread == NULL) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (hw_priv->bh_error) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
#ifdef MCAST_FWDING
|
||
|
xradio_for_each_vif(hw_priv, priv, i) {
|
||
|
if (!priv)
|
||
|
continue;
|
||
|
if ((priv->multicast_filter.enable)
|
||
|
&& (priv->join_status == XRADIO_JOIN_STATUS_AP)) {
|
||
|
wsm_release_buffer_to_fw(priv,
|
||
|
(hw_priv->wsm_caps.
|
||
|
numInpChBufs - 1));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
atomic_set(&hw_priv->bh_suspend, XRADIO_BH_SUSPEND);
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
up(&hw_priv->bh_sem);
|
||
|
#else
|
||
|
wake_up(&hw_priv->bh_wq);
|
||
|
#endif
|
||
|
return wait_event_timeout(hw_priv->bh_evt_wq, (hw_priv->bh_error ||
|
||
|
XRADIO_BH_SUSPENDED == atomic_read(&hw_priv->bh_suspend)),
|
||
|
1 * HZ) ? 0 : -ETIMEDOUT;
|
||
|
}
|
||
|
|
||
|
int xradio_bh_resume(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
|
||
|
#ifdef MCAST_FWDING
|
||
|
int ret;
|
||
|
int i = 0;
|
||
|
struct xradio_vif *priv = NULL;
|
||
|
#endif
|
||
|
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
if (hw_priv->bh_error || atomic_read(&hw_priv->bh_term)) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
atomic_set(&hw_priv->bh_suspend, XRADIO_BH_RESUME);
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
up(&hw_priv->bh_sem);
|
||
|
#else
|
||
|
wake_up(&hw_priv->bh_wq);
|
||
|
#endif
|
||
|
|
||
|
#ifdef MCAST_FWDING
|
||
|
ret = wait_event_timeout(hw_priv->bh_evt_wq, (hw_priv->bh_error ||
|
||
|
XRADIO_BH_RESUMED == atomic_read(&hw_priv->bh_suspend)), 1 * HZ) ?
|
||
|
0 : -ETIMEDOUT;
|
||
|
|
||
|
xradio_for_each_vif(hw_priv, priv, i) {
|
||
|
if (!priv)
|
||
|
continue;
|
||
|
if ((priv->join_status == XRADIO_JOIN_STATUS_AP) &&
|
||
|
(priv->multicast_filter.enable)) {
|
||
|
u8 count = 0;
|
||
|
SYS_WARN(wsm_request_buffer_request(priv, &count));
|
||
|
bh_printk(XRADIO_DBG_NIY, "Reclaim Buff %d \n", count);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
#else
|
||
|
return wait_event_timeout(hw_priv->bh_evt_wq, hw_priv->bh_error ||
|
||
|
(XRADIO_BH_RESUMED == atomic_read(&hw_priv->bh_suspend)),
|
||
|
1 * HZ) ? 0 : -ETIMEDOUT;
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
static inline void wsm_alloc_tx_buffer(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
++hw_priv->hw_bufs_used;
|
||
|
}
|
||
|
|
||
|
int wsm_release_tx_buffer(struct xradio_common *hw_priv, int count)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
int hw_bufs_used = hw_priv->hw_bufs_used;
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
hw_priv->hw_bufs_used -= count;
|
||
|
if (SYS_WARN(hw_priv->hw_bufs_used < 0)) {
|
||
|
/* Tx data patch stops when all but one hw buffers are used.
|
||
|
So, re-start tx path in case we find hw_bufs_used equals
|
||
|
numInputChBufs - 1.
|
||
|
*/
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s, hw_bufs_used=%d, count=%d.\n",
|
||
|
__func__, hw_priv->hw_bufs_used, count);
|
||
|
ret = -1;
|
||
|
} else if (hw_bufs_used >= (hw_priv->wsm_caps.numInpChBufs - 1))
|
||
|
ret = 1;
|
||
|
if (!hw_priv->hw_bufs_used)
|
||
|
wake_up(&hw_priv->bh_evt_wq);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int wsm_release_vif_tx_buffer(struct xradio_common *hw_priv,
|
||
|
int if_id, int count)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
hw_priv->hw_bufs_used_vif[if_id] -= count;
|
||
|
if (!hw_priv->hw_bufs_used_vif[if_id])
|
||
|
wake_up(&hw_priv->bh_evt_wq);
|
||
|
|
||
|
if (hw_priv->hw_bufs_used_vif[if_id] < 0) {
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"%s, if=%d, used=%d, count=%d.\n", __func__, if_id,
|
||
|
hw_priv->hw_bufs_used_vif[if_id], count);
|
||
|
ret = -1;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#ifdef MCAST_FWDING
|
||
|
int wsm_release_buffer_to_fw(struct xradio_vif *priv, int count)
|
||
|
{
|
||
|
int i;
|
||
|
u8 flags;
|
||
|
struct wsm_hdr *wsm;
|
||
|
struct xradio_common *hw_priv = priv->hw_priv;
|
||
|
struct wsm_buf *buf = &hw_priv->wsm_release_buf;
|
||
|
size_t buf_len = buf->end - buf->begin;
|
||
|
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
if (priv->join_status != XRADIO_JOIN_STATUS_AP || buf_len == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
bh_printk(XRADIO_DBG_NIY, "Rel buffer to FW %d, %d\n",
|
||
|
count, hw_priv->hw_bufs_used);
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
if ((hw_priv->hw_bufs_used + 1) < hw_priv->wsm_caps.numInpChBufs) {
|
||
|
/* Fill Buffer Request Msg */
|
||
|
flags = i ? 0 : 0x1;
|
||
|
buf->data[0] = flags;
|
||
|
|
||
|
/* Add sequence number */
|
||
|
wsm = (struct wsm_hdr *)buf->begin;
|
||
|
wsm->id &= __cpu_to_le32(~WSM_TX_SEQ(WSM_TX_SEQ_MAX));
|
||
|
wsm->id |= cpu_to_le32(WSM_TX_SEQ(hw_priv->wsm_tx_seq));
|
||
|
bh_printk(XRADIO_DBG_NIY, "REL %d, len=%d, buflen=%zu\n",
|
||
|
hw_priv->wsm_tx_seq, wsm->len, buf_len);
|
||
|
|
||
|
wsm_alloc_tx_buffer(hw_priv);
|
||
|
if (SYS_WARN(xradio_data_write(hw_priv, buf->begin, buf_len))) {
|
||
|
break;
|
||
|
}
|
||
|
hw_priv->buf_released = 1;
|
||
|
hw_priv->wsm_tx_seq = (hw_priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
|
||
|
} else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (i == count) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Should not be here */
|
||
|
bh_printk(XRADIO_DBG_ERROR, "Error, Less HW buf %d,%d.\n",
|
||
|
hw_priv->hw_bufs_used, hw_priv->wsm_caps.numInpChBufs);
|
||
|
SYS_WARN(1);
|
||
|
return -1;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* reserve a packet for the case dev_alloc_skb failed in bh.*/
|
||
|
int xradio_init_resv_skb(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
int len = SKB_RESV_MAX + WSM_TX_EXTRA_HEADROOM + \
|
||
|
8 + 12; /* TKIP IV + ICV and MIC */
|
||
|
len = xr_sdio_blksize_align(len);
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
spin_lock_init(&hw_priv->cache_lock);
|
||
|
hw_priv->skb_reserved = xr_alloc_skb(len);
|
||
|
if (hw_priv->skb_reserved) {
|
||
|
hw_priv->skb_resv_len = len;
|
||
|
skb_reserve(hw_priv->skb_reserved,
|
||
|
WSM_TX_EXTRA_HEADROOM + 8 /* TKIP IV */
|
||
|
- WSM_RX_EXTRA_HEADROOM);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_WARN, "%s xr_alloc_skb failed(%d)\n",
|
||
|
__func__, len);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void xradio_deinit_resv_skb(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
if (hw_priv->skb_reserved) {
|
||
|
dev_kfree_skb(hw_priv->skb_reserved);
|
||
|
hw_priv->skb_reserved = NULL;
|
||
|
hw_priv->skb_resv_len = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int xradio_realloc_resv_skb(struct xradio_common *hw_priv,
|
||
|
struct sk_buff *skb, u8 flags)
|
||
|
{
|
||
|
/* spin_lock(&hw_priv->cache_lock); */
|
||
|
if (!hw_priv->skb_reserved && hw_priv->skb_resv_len) {
|
||
|
hw_priv->skb_reserved = xr_alloc_skb(hw_priv->skb_resv_len);
|
||
|
if (!hw_priv->skb_reserved && (flags & ITEM_F_RESERVE)) {
|
||
|
hw_priv->skb_reserved = skb;
|
||
|
skb_reserve(hw_priv->skb_reserved,
|
||
|
WSM_TX_EXTRA_HEADROOM + 8 /* TKIP IV */
|
||
|
- WSM_RX_EXTRA_HEADROOM);
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
bh_printk(XRADIO_DBG_WARN, "%s xr_alloc_skb failed(%d)\n",
|
||
|
__func__, hw_priv->skb_resv_len);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
return 0; /* realloc sbk success, deliver to upper.*/
|
||
|
}
|
||
|
|
||
|
static inline struct sk_buff *xradio_get_resv_skb(struct xradio_common *hw_priv,
|
||
|
size_t len)
|
||
|
{ struct sk_buff *skb = NULL;
|
||
|
/* spin_lock(&hw_priv->cache_lock); */
|
||
|
if (hw_priv->skb_reserved && len <= hw_priv->skb_resv_len) {
|
||
|
skb = hw_priv->skb_reserved;
|
||
|
hw_priv->skb_reserved = NULL;
|
||
|
}
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
return skb;
|
||
|
}
|
||
|
|
||
|
static inline int xradio_put_resv_skb(struct xradio_common *hw_priv,
|
||
|
struct sk_buff *skb, u8 flags)
|
||
|
{
|
||
|
/* spin_lock(&hw_priv->cache_lock); */
|
||
|
if (!hw_priv->skb_reserved && hw_priv->skb_resv_len &&
|
||
|
(flags & ITEM_F_RESERVE)) {
|
||
|
hw_priv->skb_reserved = skb;
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
return 0;
|
||
|
}
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
return 1; /* sbk not put to reserve*/
|
||
|
}
|
||
|
|
||
|
static struct sk_buff *xradio_get_skb(struct xradio_common *hw_priv, size_t len, u8 *flags)
|
||
|
{
|
||
|
struct sk_buff *skb = NULL;
|
||
|
size_t alloc_len = (len > SKB_CACHE_LEN) ? len : SKB_CACHE_LEN;
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
/* TKIP IV + TKIP ICV and MIC - Piggyback.*/
|
||
|
alloc_len += WSM_TX_EXTRA_HEADROOM + 8 + 12 - 2;
|
||
|
if (len > SKB_CACHE_LEN || !hw_priv->skb_cache) {
|
||
|
skb = xr_alloc_skb_pf(alloc_len);
|
||
|
/* In AP mode RXed SKB can be looped back as a broadcast.
|
||
|
* Here we reserve enough space for headers. */
|
||
|
if (skb) {
|
||
|
skb_reserve(skb, WSM_TX_EXTRA_HEADROOM + 8 /* TKIP IV */
|
||
|
- WSM_RX_EXTRA_HEADROOM);
|
||
|
} else {
|
||
|
skb = xradio_get_resv_skb(hw_priv, alloc_len);
|
||
|
if (skb) {
|
||
|
*flags |= ITEM_F_RESERVE;
|
||
|
bh_printk(XRADIO_DBG_WARN, "%s get skb_reserved(%zu)!\n",
|
||
|
__func__, alloc_len);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s xr_alloc_skb failed(%zu)!\n",
|
||
|
__func__, alloc_len);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/* don't care cache because min len is SKB_CACHE_LEN*/
|
||
|
/* spin_lock(&hw_priv->cache_lock); */
|
||
|
skb = hw_priv->skb_cache;
|
||
|
hw_priv->skb_cache = NULL;
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
}
|
||
|
return skb;
|
||
|
}
|
||
|
|
||
|
static void xradio_put_skb(struct xradio_common *hw_priv, struct sk_buff *skb)
|
||
|
{
|
||
|
bh_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
/* spin_lock(&hw_priv->cache_lock); */
|
||
|
if (hw_priv->skb_cache)
|
||
|
dev_kfree_skb(skb);
|
||
|
else {
|
||
|
hw_priv->skb_cache = skb;
|
||
|
}
|
||
|
/* spin_unlock(&hw_priv->cache_lock); */
|
||
|
}
|
||
|
|
||
|
static int xradio_bh_read_ctrl_reg(struct xradio_common *hw_priv,
|
||
|
u16 *ctrl_reg)
|
||
|
{
|
||
|
int ret;
|
||
|
ret = xradio_reg_read_16(hw_priv, HIF_CONTROL_REG_ID, ctrl_reg);
|
||
|
if (ret) {
|
||
|
*ctrl_reg = 0;
|
||
|
ret = 0;
|
||
|
bh_printk(XRADIO_DBG_NIY, "read ctrl failed, SDIO DCE occupied!\n");
|
||
|
ret = xradio_reg_read_16(hw_priv, HIF_CONTROL_REG_ID, ctrl_reg);
|
||
|
if (ret) {
|
||
|
hw_priv->bh_error = 1;
|
||
|
bh_printk(XRADIO_DBG_ERROR, "Failed to read control register.\n");
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static inline int xradio_device_sleep(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
int ret;
|
||
|
ret = xradio_reg_bit_operate(hw_priv, HIF_CONTROL_REG_ID,
|
||
|
0, HIF_CTRL_WUP_BIT);
|
||
|
if (ret) {
|
||
|
hw_priv->bh_error = 1;
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s:control reg failed.\n", __func__);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
struct timeval wakeup_time;
|
||
|
static int xradio_device_wakeup(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u16 ctrl_reg = 0;
|
||
|
unsigned long time = 0;
|
||
|
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
|
||
|
PERF_INFO_GETTIME(&wakeup_time);
|
||
|
/* To force the device to be always-on, the host sets WLAN_UP to 1 */
|
||
|
ret = xradio_reg_write_16(hw_priv, HIF_CONTROL_REG_ID, HIF_CTRL_WUP_BIT);
|
||
|
if (SYS_WARN(ret))
|
||
|
return ret;
|
||
|
|
||
|
ret = xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
if (SYS_WARN(ret))
|
||
|
return ret;
|
||
|
|
||
|
/* If the device returns WLAN_RDY as 1, the device is active and will
|
||
|
* remain active. */
|
||
|
time = jiffies + DEV_WAKEUP_MAX_TIME;
|
||
|
while (!(ctrl_reg & (HIF_CTRL_RDY_BIT|HIF_CTRL_NEXT_LEN_MASK)) &&
|
||
|
time_before(jiffies, time) && !ret) {
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
msleep(1);
|
||
|
#else
|
||
|
wait_event_timeout(hw_priv->bh_wq,
|
||
|
atomic_read(&hw_priv->bh_rx), DEV_WAKEUP_WAIT_TIME);
|
||
|
#endif
|
||
|
ret = xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
}
|
||
|
|
||
|
PERF_INFO_STAMP(&wakeup_time, &dev_wake, 0);
|
||
|
|
||
|
if (likely(ctrl_reg & HIF_CTRL_RDY_BIT)) {
|
||
|
bh_printk(XRADIO_DBG_NIY, "Device awake, t=%ldms.\n",
|
||
|
(jiffies+DEV_WAKEUP_MAX_TIME-time)*1000/HZ);
|
||
|
return 1;
|
||
|
} else if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) { /*device has data to rx.*/
|
||
|
bh_printk(XRADIO_DBG_NIY, "To rx data before wakeup, len=%d.\n",
|
||
|
(ctrl_reg & HIF_CTRL_NEXT_LEN_MASK)<<1);
|
||
|
return (int)(ctrl_reg & HIF_CTRL_NEXT_LEN_MASK);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "Device cannot wakeup in %dms.\n",
|
||
|
DEV_WAKEUP_MAX_TIME*1000/HZ);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef BH_COMINGRX_FORECAST
|
||
|
static bool xradio_comingrx_update(struct xradio_common *hw_priv)
|
||
|
{
|
||
|
static bool is_full;
|
||
|
static unsigned long tmo;
|
||
|
if (hw_priv->hw_bufs_used >= (hw_priv->wsm_caps.numInpChBufs-1)) {
|
||
|
if (is_full == false) {
|
||
|
tmo = jiffies + (HZ/166);/*1/166s = 6ms*/
|
||
|
}
|
||
|
is_full = true;
|
||
|
} else {
|
||
|
tmo = jiffies - 1;
|
||
|
is_full = false;
|
||
|
}
|
||
|
|
||
|
if (time_before(jiffies, tmo))
|
||
|
return true;
|
||
|
else
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Must be called from BH thraed. */
|
||
|
void xradio_enable_powersave(struct xradio_vif *priv, bool enable)
|
||
|
{
|
||
|
priv->powersave_enabled = enable;
|
||
|
bh_printk(XRADIO_DBG_NIY, "Powerave is %s.\n",
|
||
|
enable ? "enabled" : "disabled");
|
||
|
}
|
||
|
|
||
|
#if PERF_INFO_TEST
|
||
|
struct timeval tx_start_time1;
|
||
|
struct timeval tx_start_time2;
|
||
|
struct timeval rx_start_time1;
|
||
|
struct timeval rx_start_time2;
|
||
|
struct timeval bh_start_time;
|
||
|
struct timeval sdio_reg_time;
|
||
|
extern struct timeval last_showtime;
|
||
|
#endif
|
||
|
|
||
|
u32 sdio_reg_cnt1;
|
||
|
u32 sdio_reg_cnt2;
|
||
|
u32 sdio_reg_cnt3;
|
||
|
u32 sdio_reg_cnt4;
|
||
|
u32 sdio_reg_cnt5;
|
||
|
u32 sdio_reg_cnt6;
|
||
|
u32 tx_limit_cnt1;
|
||
|
u32 tx_limit_cnt2;
|
||
|
u32 tx_limit_cnt3;
|
||
|
u32 tx_limit_cnt4;
|
||
|
u32 tx_limit_cnt5;
|
||
|
u32 tx_limit_cnt6;
|
||
|
|
||
|
static int xradio_bh(void *arg)
|
||
|
{
|
||
|
struct xradio_common *hw_priv = arg;
|
||
|
struct sched_param param = {
|
||
|
.sched_priority = 1
|
||
|
};
|
||
|
int ret = 0;
|
||
|
struct sk_buff *skb_rx = NULL;
|
||
|
size_t read_len = 0;
|
||
|
int rx = 0, tx = 0, term, suspend;
|
||
|
struct wsm_hdr *wsm;
|
||
|
size_t wsm_len;
|
||
|
int wsm_id;
|
||
|
u8 wsm_seq;
|
||
|
int rx_resync = 1;
|
||
|
u16 ctrl_reg = 0;
|
||
|
int tx_allowed;
|
||
|
int pending_tx = 0;
|
||
|
int tx_burst;
|
||
|
int tx_bursted = 0;
|
||
|
int rx_burst = 0;
|
||
|
long status;
|
||
|
bool coming_rx = false;
|
||
|
#if 0
|
||
|
u32 dummy;
|
||
|
#endif
|
||
|
int reg_read = 1;
|
||
|
int vif_selected;
|
||
|
|
||
|
bh_printk(XRADIO_DBG_MSG, "%s\n", __func__);
|
||
|
ret = sched_setscheduler(hw_priv->bh_thread, SCHED_FIFO, ¶m);
|
||
|
if (ret)
|
||
|
bh_printk(XRADIO_DBG_WARN, "%s sched_setscheduler failed(%d)\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
PERF_INFO_GETTIME(&last_showtime);
|
||
|
for (;;) {
|
||
|
PERF_INFO_GETTIME(&bh_start_time);
|
||
|
/* Check if devices can sleep, and set time to wait for interrupt. */
|
||
|
if (!hw_priv->hw_bufs_used && !pending_tx &&
|
||
|
hw_priv->powersave_enabled && !hw_priv->device_can_sleep &&
|
||
|
!atomic_read(&hw_priv->recent_scan) &&
|
||
|
atomic_read(&hw_priv->bh_rx) == 0 &&
|
||
|
atomic_read(&hw_priv->bh_tx) == 0) {
|
||
|
bh_printk(XRADIO_DBG_MSG, "Device idle, can sleep.\n");
|
||
|
SYS_WARN(xradio_device_sleep(hw_priv));
|
||
|
hw_priv->device_can_sleep = true;
|
||
|
status = (HZ>>3); /*1/8s = 125ms*/
|
||
|
} else if (hw_priv->hw_bufs_used >=
|
||
|
(hw_priv->wsm_caps.numInpChBufs - 1)) {
|
||
|
/* don't wait too long if some frames to confirm
|
||
|
* and miss interrupt.*/
|
||
|
status = (HZ>>4); /*1/16s=62ms.*/
|
||
|
} else {
|
||
|
status = (HZ>>3); /*1/8s = 125ms*/
|
||
|
}
|
||
|
|
||
|
|
||
|
#if 0
|
||
|
/* Dummy Read for SDIO retry mechanism */
|
||
|
if (atomic_read(&hw_priv->bh_rx) == 0 &&
|
||
|
atomic_read(&hw_priv->bh_tx) == 0) {
|
||
|
xradio_reg_read(hw_priv, HIF_CONFIG_REG_ID, &dummy, sizeof(dummy));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if 0
|
||
|
/* If a packet has already been txed to the device then read the
|
||
|
* control register for a probable interrupt miss before going
|
||
|
* further to wait for interrupt; if the read length is non-zero
|
||
|
* then it means there is some data to be received */
|
||
|
if (hw_priv->hw_bufs_used) {
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
++reg_read;
|
||
|
++sdio_reg_cnt1;
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
DBG_INT_ADD(fix_miss_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
} else {
|
||
|
++sdio_reg_cnt5;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifdef BH_COMINGRX_FORECAST
|
||
|
coming_rx = xradio_comingrx_update(hw_priv);
|
||
|
|
||
|
if (coming_rx) {
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
++reg_read;
|
||
|
++sdio_reg_cnt1;
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
DBG_INT_ADD(fix_miss_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
} else {
|
||
|
++sdio_reg_cnt5;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
/* Wait for Events in HZ/8 */
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
rx = atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
tx = atomic_xchg(&hw_priv->bh_tx, 0);
|
||
|
suspend = pending_tx ? 0 : atomic_read(&hw_priv->bh_suspend);
|
||
|
term = kthread_should_stop();
|
||
|
if (!(rx || tx || coming_rx || term || suspend || hw_priv->bh_error)) {
|
||
|
atomic_set(&hw_priv->bh_wk, 0);
|
||
|
status = (long)(down_timeout(&hw_priv->bh_sem, status) != -ETIME);
|
||
|
rx = atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
tx = atomic_xchg(&hw_priv->bh_tx, 0);
|
||
|
suspend = pending_tx ? 0 : atomic_read(&hw_priv->bh_suspend);
|
||
|
term = kthread_should_stop();
|
||
|
}
|
||
|
#else
|
||
|
status = wait_event_interruptible_timeout(hw_priv->bh_wq, ({
|
||
|
rx = atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
tx = atomic_xchg(&hw_priv->bh_tx, 0);
|
||
|
term = kthread_should_stop();
|
||
|
suspend = pending_tx ? 0 : atomic_read(&hw_priv->bh_suspend);
|
||
|
(rx || tx || coming_rx || term || suspend || hw_priv->bh_error); }),
|
||
|
status);
|
||
|
#endif
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &bh_wait, 0);
|
||
|
|
||
|
/* 0--bh is going to be shut down */
|
||
|
if (term) {
|
||
|
bh_printk(XRADIO_DBG_MSG, "xradio_bh exit!\n");
|
||
|
break;
|
||
|
}
|
||
|
/* 1--An fatal error occurs */
|
||
|
if (status < 0 || hw_priv->bh_error) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "bh_error=%d, status=%ld\n",
|
||
|
hw_priv->bh_error, status);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* 2--Wait for interrupt time out */
|
||
|
if (!status) {
|
||
|
DBG_INT_ADD(bh_idle);
|
||
|
/* Check if miss interrupt. */
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
++reg_read;
|
||
|
++sdio_reg_cnt2;
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
bh_printk(XRADIO_DBG_WARN, "miss interrupt!\n");
|
||
|
DBG_INT_ADD(int_miss_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
} else {
|
||
|
++sdio_reg_cnt5;
|
||
|
}
|
||
|
|
||
|
/* There are some frames to be confirmed. */
|
||
|
if (hw_priv->hw_bufs_used) {
|
||
|
long timeout = 0;
|
||
|
bool pending = 0;
|
||
|
bh_printk(XRADIO_DBG_NIY, "Need confirm:%d!\n",
|
||
|
hw_priv->hw_bufs_used);
|
||
|
/* Check if frame transmission is timed out. */
|
||
|
pending = xradio_query_txpkt_timeout(hw_priv, XRWL_ALL_IFS,
|
||
|
hw_priv->pending_frame_id, &timeout);
|
||
|
/* There are some frames confirm time out. */
|
||
|
if (pending && timeout < 0) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"query_txpkt_timeout:%ld!\n", timeout);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
rx = 1; /* Go to check rx again. */
|
||
|
} else if (!pending_tx) {
|
||
|
if (hw_priv->powersave_enabled &&
|
||
|
!hw_priv->device_can_sleep &&
|
||
|
!atomic_read(&hw_priv->recent_scan)) {
|
||
|
/* Device is idle, we can go to sleep. */
|
||
|
bh_printk(XRADIO_DBG_MSG,
|
||
|
"Device idle(timeout), can sleep.\n");
|
||
|
SYS_WARN(xradio_device_sleep(hw_priv));
|
||
|
hw_priv->device_can_sleep = true;
|
||
|
}
|
||
|
PERF_INFO_STAMP(&bh_start_time, &bh_others, 0);
|
||
|
continue;
|
||
|
}
|
||
|
/* 3--Host suspend request. */
|
||
|
} else if (suspend) {
|
||
|
bh_printk(XRADIO_DBG_NIY, "Host suspend request.\n");
|
||
|
/* Check powersave setting again. */
|
||
|
if (hw_priv->powersave_enabled) {
|
||
|
bh_printk(XRADIO_DBG_MSG,
|
||
|
"Device idle(host suspend), can sleep.\n");
|
||
|
SYS_WARN(xradio_device_sleep(hw_priv));
|
||
|
hw_priv->device_can_sleep = true;
|
||
|
}
|
||
|
|
||
|
/* bh thread go to suspend. */
|
||
|
atomic_set(&hw_priv->bh_suspend, XRADIO_BH_SUSPENDED);
|
||
|
wake_up(&hw_priv->bh_evt_wq);
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
do {
|
||
|
status = down_timeout(&hw_priv->bh_sem, HZ/10);
|
||
|
term = kthread_should_stop();
|
||
|
} while (XRADIO_BH_RESUME != atomic_read(&hw_priv->bh_suspend) &&
|
||
|
!term && !hw_priv->bh_error);
|
||
|
if (XRADIO_BH_RESUME != atomic_read(&hw_priv->bh_suspend))
|
||
|
status = -1;
|
||
|
else
|
||
|
status = 0;
|
||
|
#else
|
||
|
status = wait_event_interruptible(hw_priv->bh_wq, ({
|
||
|
term = kthread_should_stop();
|
||
|
(XRADIO_BH_RESUME == atomic_read(&hw_priv->bh_suspend) ||
|
||
|
term || hw_priv->bh_error); }));
|
||
|
#endif
|
||
|
if (hw_priv->bh_error) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "bh error during bh suspend.\n");
|
||
|
break;
|
||
|
} else if (term) {
|
||
|
bh_printk(XRADIO_DBG_WARN, "bh exit during bh suspend.\n");
|
||
|
break;
|
||
|
} else if (status < 0) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"Failed to wait for resume: %ld.\n", status);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
bh_printk(XRADIO_DBG_NIY, "Host resume.\n");
|
||
|
atomic_set(&hw_priv->bh_suspend, XRADIO_BH_RESUMED);
|
||
|
wake_up(&hw_priv->bh_evt_wq);
|
||
|
atomic_add(1, &hw_priv->bh_rx);
|
||
|
continue;
|
||
|
}
|
||
|
/* query stuck frames in firmware. */
|
||
|
if (atomic_xchg(&hw_priv->query_cnt, 0)) {
|
||
|
if (schedule_work(&hw_priv->query_work) <= 0)
|
||
|
atomic_add(1, &hw_priv->query_cnt);
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
/* If a packet has already been txed to the device then read the
|
||
|
* control register for a probable interrupt miss before going
|
||
|
* further to wait for interrupt; if the read length is non-zero
|
||
|
* then it means there is some data to be received */
|
||
|
if ((hw_priv->wsm_caps.numInpChBufs -
|
||
|
hw_priv->hw_bufs_used) <= 1 && !reg_read) {
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
++sdio_reg_cnt1;
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
DBG_INT_ADD(fix_miss_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
} else {
|
||
|
++sdio_reg_cnt5;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* 4--Rx & Tx process. */
|
||
|
data_proc:
|
||
|
term = kthread_should_stop();
|
||
|
if (hw_priv->bh_error || term)
|
||
|
break;
|
||
|
/*pre-txrx*/
|
||
|
tx_bursted = 0;
|
||
|
|
||
|
rx += atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
if (rx) {
|
||
|
size_t alloc_len;
|
||
|
u8 *data;
|
||
|
u8 flags;
|
||
|
|
||
|
/* Check ctrl_reg again. */
|
||
|
if (!(ctrl_reg & HIF_CTRL_NEXT_LEN_MASK)) {
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
if (SYS_WARN(xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg))) {
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
++reg_read;
|
||
|
++sdio_reg_cnt3;
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
}
|
||
|
PERF_INFO_STAMP(&bh_start_time, &bh_others, 0);
|
||
|
|
||
|
/* read_len=ctrl_reg*2.*/
|
||
|
read_len = (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK)<<1;
|
||
|
if (!read_len) {
|
||
|
++sdio_reg_cnt6;
|
||
|
rx = 0;
|
||
|
goto tx;
|
||
|
}
|
||
|
|
||
|
rx:
|
||
|
reg_read = 0;
|
||
|
flags = 0;
|
||
|
PERF_INFO_GETTIME(&rx_start_time1);
|
||
|
if (SYS_WARN((read_len < sizeof(struct wsm_hdr)) ||
|
||
|
(read_len > EFFECTIVE_BUF_SIZE))) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "Invalid read len: %zu", read_len);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
#if BH_PROC_RX
|
||
|
if (unlikely(atomic_read(&hw_priv->proc.rx_queued) >=
|
||
|
((ITEM_RESERVED*PROC_POOL_NUM) - XRWL_MAX_QUEUE_SZ - 1))) {
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"Too many rx packets, proc cannot handle in time!\n");
|
||
|
msleep(10);
|
||
|
goto tx; /* too many rx packets to be handled, do tx first*/
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
|
||
|
* to the NEXT Message length + 2 Bytes for SKB */
|
||
|
read_len = read_len + 2;
|
||
|
alloc_len = hw_priv->sbus_ops->align_size(hw_priv->sbus_priv,
|
||
|
read_len);
|
||
|
/* Check if not exceeding XRADIO capabilities */
|
||
|
if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR,
|
||
|
"Read aligned len: %zu\n", alloc_len);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_MSG,
|
||
|
"Rx len=%zu, aligned len=%zu\n",
|
||
|
read_len, alloc_len);
|
||
|
}
|
||
|
|
||
|
/* Get skb buffer. */
|
||
|
skb_rx = xradio_get_skb(hw_priv, alloc_len, &flags);
|
||
|
if (SYS_WARN(!skb_rx)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "xradio_get_skb failed.\n");
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
skb_trim(skb_rx, 0);
|
||
|
skb_put(skb_rx, read_len);
|
||
|
data = skb_rx->data;
|
||
|
if (SYS_WARN(!data)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "skb data is NULL.\n");
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
PERF_INFO_STAMP(&rx_start_time1, &prepare_rx, alloc_len);
|
||
|
|
||
|
/* Read data from device. */
|
||
|
PERF_INFO_GETTIME(&rx_start_time2);
|
||
|
if (SYS_WARN(xradio_data_read(hw_priv, data, alloc_len))) {
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
DBG_INT_ADD(rx_total_cnt);
|
||
|
|
||
|
PERF_INFO_STAMP_UPDATE(&rx_start_time2, &sdio_read, alloc_len);
|
||
|
|
||
|
/* Piggyback */
|
||
|
ctrl_reg = __le16_to_cpu(((__le16 *)data)[(alloc_len >> 1) - 1]);
|
||
|
|
||
|
/* check wsm length. */
|
||
|
wsm = (struct wsm_hdr *)data;
|
||
|
wsm_len = __le32_to_cpu(wsm->len);
|
||
|
if (SYS_WARN(wsm_len > read_len)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "wsm_id=0x%04x, wsm_len=%zu.\n",
|
||
|
(__le32_to_cpu(wsm->id) & 0xFFF), wsm_len);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* dump rx data. */
|
||
|
#if defined(CONFIG_XRADIO_DEBUG)
|
||
|
if (unlikely(hw_priv->wsm_enable_wsm_dumps)) {
|
||
|
u16 msgid, ifid;
|
||
|
u16 *p = (u16 *) data;
|
||
|
msgid = (*(p + 1)) & WSM_MSG_ID_MASK;
|
||
|
ifid = (*(p + 1)) >> 6;
|
||
|
ifid &= 0xF;
|
||
|
bh_printk(XRADIO_DBG_ALWY,
|
||
|
"[DUMP] msgid 0x%.4X ifid %d len %d\n",
|
||
|
msgid, ifid, *p);
|
||
|
print_hex_dump_bytes("<-- ", DUMP_PREFIX_NONE, data,
|
||
|
min(wsm_len, (size_t)hw_priv->wsm_dump_max_size));
|
||
|
}
|
||
|
#endif /* CONFIG_XRADIO_DEBUG */
|
||
|
|
||
|
/* extract wsm id and seq. */
|
||
|
wsm_id = __le32_to_cpu(wsm->id) & 0xFFF;
|
||
|
wsm_seq = (__le32_to_cpu(wsm->id) >> 13) & 7;
|
||
|
/* for multi-rx indication, there two case.*/
|
||
|
if (ROUND4(wsm_len) < read_len - 2)
|
||
|
skb_trim(skb_rx, read_len - 2);
|
||
|
else
|
||
|
skb_trim(skb_rx, wsm_len);
|
||
|
|
||
|
/* process exceptions. */
|
||
|
if (unlikely(wsm_id == 0x0800)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "firmware exception!\n");
|
||
|
wsm_handle_exception(hw_priv, &data[sizeof(*wsm)],
|
||
|
wsm_len - sizeof(*wsm));
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
} else if (likely(!rx_resync)) {
|
||
|
if (SYS_WARN(wsm_seq != hw_priv->wsm_rx_seq)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "wsm_seq=%d.\n", wsm_seq);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
hw_priv->wsm_rx_seq = (wsm_seq + 1) & 7;
|
||
|
rx_resync = 0;
|
||
|
#if (DGB_XRADIO_HWT)
|
||
|
rx_resync = 1; /*0 -> 1, HWT test, should not check this.*/
|
||
|
#endif
|
||
|
|
||
|
/* Process tx frames confirm. */
|
||
|
if (wsm_id & 0x0400) {
|
||
|
int rc = 0;
|
||
|
int if_id = 0;
|
||
|
u32 *cfm = (u32 *)(wsm + 1);
|
||
|
wsm_id &= ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX);
|
||
|
if (wsm_id == 0x041E) {
|
||
|
int cfm_cnt = *cfm;
|
||
|
struct wsm_tx_confirm *tx_cfm =
|
||
|
(struct wsm_tx_confirm *)(cfm + 1);
|
||
|
bh_printk(XRADIO_DBG_NIY, "multi-cfm %d.\n", cfm_cnt);
|
||
|
|
||
|
rc = wsm_release_tx_buffer(hw_priv, cfm_cnt);
|
||
|
do {
|
||
|
if_id = xradio_queue_get_if_id(tx_cfm->packetID);
|
||
|
wsm_release_vif_tx_buffer(hw_priv, if_id, 1);
|
||
|
tx_cfm = (struct wsm_tx_confirm *)((u8 *)tx_cfm +
|
||
|
offsetof(struct wsm_tx_confirm, link_id));
|
||
|
} while (--cfm_cnt);
|
||
|
} else {
|
||
|
rc = wsm_release_tx_buffer(hw_priv, 1);
|
||
|
if (wsm_id == 0x0404) {
|
||
|
if_id = xradio_queue_get_if_id(*cfm);
|
||
|
wsm_release_vif_tx_buffer(hw_priv, if_id, 1);
|
||
|
} else {
|
||
|
#if BH_PROC_RX
|
||
|
flags |= ITEM_F_CMDCFM;
|
||
|
#endif
|
||
|
}
|
||
|
bh_printk(XRADIO_DBG_NIY, "cfm id=0x%04x.\n", wsm_id);
|
||
|
}
|
||
|
if (SYS_WARN(rc < 0)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "tx buffer < 0.\n");
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
} else if (rc > 0) {
|
||
|
tx = 1;
|
||
|
xradio_proc_wakeup(hw_priv);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* WSM processing frames. */
|
||
|
#if BH_PROC_RX
|
||
|
if (SYS_WARN(xradio_bh_put(hw_priv, &skb_rx, flags))) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "xradio_bh_put failed.\n");
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
#else
|
||
|
if (SYS_WARN(wsm_handle_rx(hw_priv, flags, &skb_rx))) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "wsm_handle_rx failed.\n");
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
/* Reclaim the SKB buffer */
|
||
|
if (skb_rx) {
|
||
|
if (xradio_put_resv_skb(hw_priv, skb_rx, flags))
|
||
|
xradio_put_skb(hw_priv, skb_rx);
|
||
|
skb_rx = NULL;
|
||
|
}
|
||
|
#endif
|
||
|
PERF_INFO_STAMP(&rx_start_time2, &handle_rx, wsm_len);
|
||
|
PERF_INFO_STAMP(&rx_start_time1, &data_rx, wsm_len);
|
||
|
|
||
|
/* Check if rx burst */
|
||
|
read_len = (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK)<<1;
|
||
|
if (!read_len) {
|
||
|
rx = 0;
|
||
|
rx_burst = 0;
|
||
|
goto tx;
|
||
|
} else if (rx_burst) {
|
||
|
xradio_debug_rx_burst(hw_priv);
|
||
|
--rx_burst;
|
||
|
goto rx;
|
||
|
}
|
||
|
} else {
|
||
|
PERF_INFO_STAMP(&bh_start_time, &bh_others, 0);
|
||
|
}
|
||
|
|
||
|
tx:
|
||
|
SYS_BUG(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs);
|
||
|
tx += pending_tx + atomic_xchg(&hw_priv->bh_tx, 0);
|
||
|
#if BH_PROC_TX
|
||
|
tx += atomic_read(&hw_priv->proc.tx_queued);
|
||
|
#endif
|
||
|
pending_tx = 0;
|
||
|
tx_burst = hw_priv->wsm_caps.numInpChBufs - hw_priv->hw_bufs_used;
|
||
|
tx_allowed = tx_burst > 0;
|
||
|
if (tx && tx_allowed) {
|
||
|
int ret;
|
||
|
u8 *data;
|
||
|
size_t tx_len;
|
||
|
#if 0
|
||
|
int num = 0, i;
|
||
|
#endif
|
||
|
|
||
|
PERF_INFO_GETTIME(&tx_start_time1);
|
||
|
/* Wake up the devices */
|
||
|
if (hw_priv->device_can_sleep) {
|
||
|
ret = xradio_device_wakeup(hw_priv);
|
||
|
if (SYS_WARN(ret < 0)) {
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
} else if (ret == 1) {
|
||
|
hw_priv->device_can_sleep = false;
|
||
|
} else if (ret > 1) {
|
||
|
rx = 1;
|
||
|
ctrl_reg = (ret & HIF_CTRL_NEXT_LEN_MASK);
|
||
|
goto data_proc;
|
||
|
} else { /* Wait for "awake" interrupt */
|
||
|
pending_tx = tx;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
/* Increase Tx buffer */
|
||
|
wsm_alloc_tx_buffer(hw_priv);
|
||
|
|
||
|
#if (DGB_XRADIO_HWT)
|
||
|
/*hardware test.*/
|
||
|
ret = get_hwt_hif_tx(hw_priv, &data, &tx_len,
|
||
|
&tx_burst, &vif_selected);
|
||
|
if (ret <= 0)
|
||
|
#endif /*DGB_XRADIO_HWT*/
|
||
|
|
||
|
#if BH_PROC_TX
|
||
|
/* Get data to send and send it. */
|
||
|
ret = xradio_bh_get(hw_priv, &data, &tx_len, &tx_burst,
|
||
|
&vif_selected);
|
||
|
#else
|
||
|
/* Get data to send and send it. */
|
||
|
ret = wsm_get_tx(hw_priv, &data, &tx_len, &tx_burst,
|
||
|
&vif_selected);
|
||
|
#endif
|
||
|
if (ret <= 0) {
|
||
|
if (hw_priv->hw_bufs_used >= hw_priv->wsm_caps.numInpChBufs)
|
||
|
++tx_limit_cnt3;
|
||
|
#if BH_PROC_TX
|
||
|
if (list_empty(&hw_priv->proc.bh_tx))
|
||
|
++tx_limit_cnt4;
|
||
|
#endif
|
||
|
wsm_release_tx_buffer(hw_priv, 1);
|
||
|
if (SYS_WARN(ret < 0)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "get tx packet=%d.\n", ret);
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
tx = 0;
|
||
|
DBG_INT_ADD(tx_limit);
|
||
|
PERF_INFO_STAMP(&tx_start_time1, &prepare_tx, 0);
|
||
|
} else {
|
||
|
wsm = (struct wsm_hdr *)data;
|
||
|
SYS_BUG(tx_len < sizeof(*wsm));
|
||
|
if (SYS_BUG(__le32_to_cpu(wsm->len) != tx_len)) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "%s wsmlen=%u, tx_len=%zu.\n",
|
||
|
__func__, __le32_to_cpu(wsm->len), tx_len);
|
||
|
}
|
||
|
|
||
|
/* Continue to send next data if have any. */
|
||
|
atomic_add(1, &hw_priv->bh_tx);
|
||
|
|
||
|
if (tx_len <= 8)
|
||
|
tx_len = 16;
|
||
|
/* Align tx length and check it. */
|
||
|
/* HACK!!! Platform limitation.
|
||
|
* It is also supported by upper layer:
|
||
|
* there is always enough space at the end of the buffer. */
|
||
|
tx_len = hw_priv->sbus_ops->align_size(hw_priv->sbus_priv,
|
||
|
tx_len);
|
||
|
/* Check if not exceeding XRADIO capabilities */
|
||
|
if (tx_len > EFFECTIVE_BUF_SIZE) {
|
||
|
bh_printk(XRADIO_DBG_WARN,
|
||
|
"Write aligned len: %zu\n", tx_len);
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_MSG,
|
||
|
"Tx len=%d, aligned len=%zu\n",
|
||
|
wsm->len, tx_len);
|
||
|
}
|
||
|
|
||
|
/* Make sequence number. */
|
||
|
wsm->id &= __cpu_to_le32(~WSM_TX_SEQ(WSM_TX_SEQ_MAX));
|
||
|
wsm->id |= cpu_to_le32(WSM_TX_SEQ(hw_priv->wsm_tx_seq));
|
||
|
|
||
|
if ((wsm->id & WSM_MSG_ID_MASK) != 0x0004)
|
||
|
hw_priv->wsm_cmd.seq = cpu_to_le32(WSM_TX_SEQ(hw_priv->wsm_tx_seq));
|
||
|
|
||
|
PERF_INFO_STAMP(&tx_start_time1, &prepare_tx, tx_len);
|
||
|
PERF_INFO_GETTIME(&tx_start_time2);
|
||
|
/* Send the data to devices. */
|
||
|
if (SYS_WARN(xradio_data_write(hw_priv, data, tx_len))) {
|
||
|
wsm_release_tx_buffer(hw_priv, 1);
|
||
|
bh_printk(XRADIO_DBG_ERROR, "xradio_data_write failed\n");
|
||
|
hw_priv->bh_error = __LINE__;
|
||
|
break;
|
||
|
}
|
||
|
DBG_INT_ADD(tx_total_cnt);
|
||
|
PERF_INFO_STAMP(&tx_start_time2, &sdio_write, tx_len);
|
||
|
|
||
|
#if defined(CONFIG_XRADIO_DEBUG)
|
||
|
if (unlikely(hw_priv->wsm_enable_wsm_dumps)) {
|
||
|
u16 msgid, ifid;
|
||
|
u16 *p = (u16 *) data;
|
||
|
msgid = (*(p + 1)) & 0x3F;
|
||
|
ifid = (*(p + 1)) >> 6;
|
||
|
ifid &= 0xF;
|
||
|
if (msgid == 0x0006) {
|
||
|
bh_printk(XRADIO_DBG_ALWY,
|
||
|
"[DUMP] >>> msgid 0x%.4X ifid %d" \
|
||
|
"len %d MIB 0x%.4X\n",
|
||
|
msgid, ifid, *p, *(p + 2));
|
||
|
} else {
|
||
|
bh_printk(XRADIO_DBG_ALWY,
|
||
|
"[DUMP] >>> msgid 0x%.4X ifid %d " \
|
||
|
"len %d\n", msgid, ifid, *p);
|
||
|
}
|
||
|
print_hex_dump_bytes("--> ", DUMP_PREFIX_NONE, data,
|
||
|
min(__le32_to_cpu(wsm->len),
|
||
|
hw_priv->wsm_dump_max_size));
|
||
|
}
|
||
|
#endif /* CONFIG_XRADIO_DEBUG */
|
||
|
|
||
|
/* Process after data have sent. */
|
||
|
if (vif_selected != -1) {
|
||
|
hw_priv->hw_bufs_used_vif[vif_selected]++;
|
||
|
}
|
||
|
wsm_txed(hw_priv, data);
|
||
|
hw_priv->wsm_tx_seq = (hw_priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
|
||
|
|
||
|
PERF_INFO_STAMP(&tx_start_time1, &data_tx, wsm->len);
|
||
|
|
||
|
/* Check for burst. */
|
||
|
#if !BH_PROC_TX
|
||
|
/*if not proc tx, just look to burst limit.*/
|
||
|
tx_burst = 2;
|
||
|
#endif
|
||
|
if (tx_burst > 1 && tx_bursted < tx_burst_limit &&
|
||
|
(hw_priv->wsm_caps.numInpChBufs -
|
||
|
hw_priv->hw_bufs_used) > 1) {
|
||
|
xradio_debug_tx_burst(hw_priv);
|
||
|
if (rx_burst < tx_burst_limit)
|
||
|
++rx_burst;
|
||
|
++tx_bursted;
|
||
|
goto tx;
|
||
|
} else {
|
||
|
if (tx_bursted >= tx_burst_limit)
|
||
|
++tx_limit_cnt5;
|
||
|
if (tx_burst <= 1)
|
||
|
++tx_limit_cnt6;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/*no tx or not allow to tx, pending it.*/
|
||
|
pending_tx = tx;
|
||
|
if (!tx)
|
||
|
++tx_limit_cnt1;
|
||
|
if (!tx_allowed)
|
||
|
++tx_limit_cnt2;
|
||
|
}
|
||
|
|
||
|
PERF_INFO_GETTIME(&bh_start_time);
|
||
|
/*Check if there are frames to be rx. */
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
DBG_INT_ADD(next_rx_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
}
|
||
|
/*if no rx, we check tx again.*/
|
||
|
if (tx + atomic_xchg(&hw_priv->bh_tx, 0)) {
|
||
|
if (hw_priv->hw_bufs_used < (hw_priv->wsm_caps.numInpChBufs - 1)) {
|
||
|
tx = 1;
|
||
|
goto data_proc;
|
||
|
} else { /*if no tx buffer, we check rx reg.*/
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
++reg_read;
|
||
|
++sdio_reg_cnt1;
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
DBG_INT_ADD(fix_miss_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
} else {
|
||
|
++sdio_reg_cnt5;
|
||
|
}
|
||
|
}
|
||
|
if (hw_priv->hw_bufs_used < hw_priv->wsm_caps.numInpChBufs) {
|
||
|
tx = 1;
|
||
|
goto data_proc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#if 0
|
||
|
/*One more to check rx if reg has not be read. */
|
||
|
if (!reg_read && hw_priv->hw_bufs_used >=
|
||
|
(hw_priv->wsm_caps.numInpChBufs - 1)) {
|
||
|
atomic_xchg(&hw_priv->bh_rx, 0);
|
||
|
PERF_INFO_GETTIME(&sdio_reg_time);
|
||
|
xradio_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
|
||
|
++reg_read;
|
||
|
++sdio_reg_cnt4;
|
||
|
PERF_INFO_STAMP(&sdio_reg_time, &sdio_reg, 4);
|
||
|
if (ctrl_reg & HIF_CTRL_NEXT_LEN_MASK) {
|
||
|
DBG_INT_ADD(fix_miss_cnt);
|
||
|
rx = 1;
|
||
|
goto data_proc;
|
||
|
} else {
|
||
|
++sdio_reg_cnt5;
|
||
|
rx = 0;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
DBG_INT_ADD(tx_rx_idle);
|
||
|
PERF_INFO_STAMP(&bh_start_time, &bh_others, 0);
|
||
|
|
||
|
#if 0
|
||
|
if (hw_priv->wsm_caps.numInpChBufs - hw_priv->hw_bufs_used > 1 &&
|
||
|
atomic_read(&hw_priv->bh_tx) == 0 && pending_tx == 0 &&
|
||
|
!tx && atomic_read(&hw_priv->tx_lock) == 0) {
|
||
|
int i = 0;
|
||
|
for (i = 0; i < 4; ++i) {
|
||
|
if (hw_priv->tx_queue[i].num_queued - hw_priv->tx_queue[i].num_pending) {
|
||
|
bh_printk(XRADIO_DBG_NIY, "queued=%d, pending=%d, buf=%d.\n",
|
||
|
hw_priv->tx_queue[i].num_queued,
|
||
|
hw_priv->tx_queue[i].num_pending,
|
||
|
hw_priv->wsm_caps.numInpChBufs - hw_priv->hw_bufs_used);
|
||
|
tx = 1;
|
||
|
xradio_proc_wakeup(hw_priv);
|
||
|
goto data_proc;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
} /* for (;;) */
|
||
|
|
||
|
/* Free the SKB buffer when exit. */
|
||
|
if (skb_rx) {
|
||
|
dev_kfree_skb(skb_rx);
|
||
|
skb_rx = NULL;
|
||
|
}
|
||
|
|
||
|
/* If BH Error, handle it. */
|
||
|
if (!term) {
|
||
|
bh_printk(XRADIO_DBG_ERROR, "Fatal error, exitting code=%d.\n",
|
||
|
hw_priv->bh_error);
|
||
|
|
||
|
#ifdef SUPPORT_FW_DBG_INF
|
||
|
xradio_fw_dbg_dump_in_direct_mode(hw_priv);
|
||
|
#endif
|
||
|
|
||
|
#ifdef HW_ERROR_WIFI_RESET
|
||
|
/* notify upper layer to restart wifi.
|
||
|
* don't do it in debug version. */
|
||
|
#ifdef CONFIG_XRADIO_ETF
|
||
|
/* we should restart manually in etf mode.*/
|
||
|
if (!etf_is_connect() &&
|
||
|
XRADIO_BH_RESUMED == atomic_read(&hw_priv->bh_suspend)) {
|
||
|
wsm_upper_restart(hw_priv);
|
||
|
}
|
||
|
#else
|
||
|
if (XRADIO_BH_RESUMED == atomic_read(&hw_priv->bh_suspend))
|
||
|
wsm_upper_restart(hw_priv);
|
||
|
#endif
|
||
|
#endif
|
||
|
/* TODO: schedule_work(recovery) */
|
||
|
#ifndef HAS_PUT_TASK_STRUCT
|
||
|
/* The only reason of having this stupid code here is
|
||
|
* that __put_task_struct is not exported by kernel. */
|
||
|
for (;;) {
|
||
|
#ifdef BH_USE_SEMAPHORE
|
||
|
status = down_timeout(&hw_priv->bh_sem, HZ/10);
|
||
|
term = kthread_should_stop();
|
||
|
status = 0;
|
||
|
#else
|
||
|
int status = wait_event_interruptible(hw_priv->bh_wq, ({
|
||
|
term = kthread_should_stop();
|
||
|
(term); }));
|
||
|
#endif
|
||
|
if (status || term)
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
atomic_add(1, &hw_priv->bh_term); /*debug info, show bh status.*/
|
||
|
return 0;
|
||
|
}
|