1690 lines
47 KiB
C
Executable File
1690 lines
47 KiB
C
Executable File
/******************************************************************************
|
|
*
|
|
* Copyright(c) 2019 - 2021 Realtek Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*****************************************************************************/
|
|
#define _PHL_TRX_SDIO_C_
|
|
#include "../phl_headers.h"
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
#define XMIT_BUFFER_RETRY_LIMIT 0x100 /* > 0xFF: No limit */
|
|
#endif /* SDIO_TX_THREAD */
|
|
|
|
struct rtw_tx_buf {
|
|
#ifdef SDIO_TX_THREAD
|
|
_os_list list;
|
|
|
|
enum rtw_packet_type tag; /* return queue type */
|
|
u8 mgnt_pkt; /* used for xmit management frame */
|
|
u8 retry; /* TX retry count */
|
|
#endif
|
|
|
|
u8 *buffer; /* DMA:able scratch buffer */
|
|
u32 buf_len; /* buffer size */
|
|
u32 used_len; /* total valid data size */
|
|
u8 dma_ch;
|
|
u8 agg_cnt; /* bus aggregation nubmer */
|
|
#ifndef PHL_SDIO_TX_AGG_MAX
|
|
#define PHL_SDIO_TX_AGG_MAX 50
|
|
#endif
|
|
u16 pkt_len[PHL_SDIO_TX_AGG_MAX];
|
|
u8 wp_offset[PHL_SDIO_TX_AGG_MAX];
|
|
};
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
struct rtw_tx_buf_ring {
|
|
struct rtw_tx_buf *txbufblock;
|
|
u32 block_cnt_alloc; /* Total number of rtw_tx_buf allocated */
|
|
u32 total_blocks_size; /* block_cnt_alloc * sizeof(rtw_tx_buf) */
|
|
|
|
struct phl_queue idle_list;
|
|
struct phl_queue busy_list; /* ready to send buffer list */
|
|
struct phl_queue mgnt_idle_list; /* management buffer list */
|
|
struct phl_queue mgnt_busy_list; /* ready to send management buffer list */
|
|
};
|
|
#endif /* SDIO_TX_THREAD */
|
|
|
|
struct rtw_rx_buf_ring {
|
|
struct rtw_rx_buf *rxbufblock;
|
|
u32 block_cnt_alloc; /* Total number of rtw_rx_buf allocated */
|
|
u32 total_blocks_size; /* block_cnt_alloc * sizeof(rtw_rx_buf) */
|
|
|
|
struct phl_queue idle_rxbuf_list;
|
|
struct phl_queue busy_rxbuf_list;
|
|
struct phl_queue pend_rxbuf_list;
|
|
};
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
void phl_tx_sdio_wake_thrd(struct phl_info_t *phl_info)
|
|
{
|
|
struct hci_info_t *hci = phl_info->hci;
|
|
|
|
|
|
_os_sema_up(phl_to_drvpriv(phl_info), &hci->tx_thrd_sema);
|
|
}
|
|
|
|
static void enqueue_txbuf(struct phl_info_t *phl_info,
|
|
struct phl_queue *pool_list, struct rtw_tx_buf *txbuf,
|
|
enum list_pos pos)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
|
|
|
|
pq_push(drv, pool_list, &txbuf->list, pos, _ps);
|
|
}
|
|
|
|
static struct rtw_tx_buf *dequeue_txbuf(struct phl_info_t *phl_info,
|
|
struct phl_queue *pool_list)
|
|
{
|
|
struct rtw_tx_buf *txbuf = NULL;
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
_os_list *buflist;
|
|
u8 res;
|
|
|
|
|
|
res = pq_pop(drv, pool_list, &buflist, _first, _ps);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
txbuf = list_entry(buflist, struct rtw_tx_buf, list);
|
|
|
|
return txbuf;
|
|
}
|
|
|
|
static struct rtw_tx_buf* alloc_txbuf(struct phl_info_t *phl_info,
|
|
struct rtw_tx_buf_ring *pool, u8 tid)
|
|
{
|
|
struct rtw_tx_buf *txbuf = NULL;
|
|
|
|
|
|
if (tid == RTW_PHL_RING_CAT_MGNT) {
|
|
txbuf = dequeue_txbuf(phl_info, &pool->mgnt_idle_list);
|
|
if (txbuf)
|
|
return txbuf;
|
|
}
|
|
|
|
txbuf = dequeue_txbuf(phl_info, &pool->idle_list);
|
|
if (!txbuf)
|
|
return NULL;
|
|
|
|
if (tid == RTW_PHL_RING_CAT_MGNT)
|
|
txbuf->mgnt_pkt = true;
|
|
|
|
return txbuf;
|
|
}
|
|
|
|
/*
|
|
* Enqueue tx buffer to queue tail and notify TX I/O thread to send.
|
|
* Usually this function would be called outside TX I/O thread.
|
|
*/
|
|
static void enqueue_busy_txbuf(struct phl_info_t *phl_info,
|
|
struct rtw_tx_buf_ring *pool,
|
|
struct rtw_tx_buf *txbuf)
|
|
{
|
|
if ((txbuf->tag == RTW_PHL_PKT_TYPE_MGNT) || (txbuf->mgnt_pkt))
|
|
enqueue_txbuf(phl_info, &pool->mgnt_busy_list, txbuf, _tail);
|
|
else
|
|
enqueue_txbuf(phl_info, &pool->busy_list, txbuf, _tail);
|
|
|
|
phl_tx_sdio_wake_thrd(phl_info);
|
|
}
|
|
|
|
/*
|
|
* Enqueue tx buffer to queue head but without notifying TX I/O thread again.
|
|
* Usually this function would be called inside TX I/O thread.
|
|
*/
|
|
static void enqueue_busy_txbuf_to_head(struct phl_info_t *phl_info,
|
|
struct rtw_tx_buf_ring *pool,
|
|
struct rtw_tx_buf *txbuf)
|
|
{
|
|
if ((txbuf->tag == RTW_PHL_PKT_TYPE_MGNT) || (txbuf->mgnt_pkt))
|
|
enqueue_txbuf(phl_info, &pool->mgnt_busy_list, txbuf, _first);
|
|
else
|
|
enqueue_txbuf(phl_info, &pool->busy_list, txbuf, _first);
|
|
}
|
|
|
|
static struct rtw_tx_buf* dequeue_busy_txbuf(struct phl_info_t *phl_info,
|
|
struct rtw_tx_buf_ring *pool)
|
|
{
|
|
struct rtw_tx_buf *txbuf = NULL;
|
|
|
|
|
|
txbuf = dequeue_txbuf(phl_info, &pool->mgnt_busy_list);
|
|
if (txbuf)
|
|
return txbuf;
|
|
|
|
return dequeue_txbuf(phl_info, &pool->busy_list);
|
|
}
|
|
|
|
static void free_txbuf(struct phl_info_t *phl_info,
|
|
struct rtw_tx_buf_ring *pool, struct rtw_tx_buf *txbuf)
|
|
{
|
|
struct phl_queue *pool_list;
|
|
|
|
|
|
txbuf->retry = 0;
|
|
txbuf->used_len = 0;
|
|
txbuf->agg_cnt = 0;
|
|
if (txbuf->tag == RTW_PHL_PKT_TYPE_MGNT) {
|
|
pool_list = &pool->mgnt_idle_list;
|
|
} else {
|
|
txbuf->mgnt_pkt = false;
|
|
pool_list = &pool->idle_list;
|
|
}
|
|
|
|
enqueue_txbuf(phl_info, pool_list, txbuf, _tail);
|
|
}
|
|
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
#ifndef RTW_WKARD_SDIO_TX_USE_YIELD
|
|
/**
|
|
* txbuf_wait - waits for idle tx buffer (w/timeout)
|
|
* @phl_info: pointer of struct phl_info_t
|
|
* @timeout: timeout value in millisecond
|
|
*
|
|
* This waits for either a tx buffer has been returned to idle list or for a
|
|
* specified timeout to expire. The timeout is in millisecond.
|
|
*
|
|
* Return: 0 if timed out, 1 if tx buffer idle list is not empty, and positive
|
|
* if completed.
|
|
*/
|
|
static int txbuf_wait(struct phl_info_t *phl_info, int timeout)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
struct hci_info_t *hci = phl_info->hci;
|
|
struct rtw_tx_buf_ring *tx_pool = (struct rtw_tx_buf_ring *)hci->txbuf_pool;
|
|
_os_event event;
|
|
_os_spinlockfg sp_flags;
|
|
int ret = 1;
|
|
|
|
|
|
_os_spinlock(drv, &hci->tx_buf_lock, _ps, &sp_flags);
|
|
if (tx_pool->idle_list.cnt == 0) {
|
|
_os_event_init(drv, &event);
|
|
hci->tx_buf_event = &event;
|
|
_os_spinunlock(drv, &hci->tx_buf_lock, _ps, &sp_flags);
|
|
ret = _os_event_wait(drv, &event, timeout);
|
|
_os_spinlock(drv, &hci->tx_buf_lock, _ps, &sp_flags);
|
|
if (hci->tx_buf_event)
|
|
hci->tx_buf_event = NULL;
|
|
_os_event_free(drv, &event);
|
|
}
|
|
_os_spinunlock(drv, &hci->tx_buf_lock, _ps, &sp_flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void txbuf_set_ready(struct phl_info_t *phl_info)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
struct hci_info_t *hci = phl_info->hci;
|
|
_os_spinlockfg sp_flags;
|
|
|
|
|
|
_os_spinlock(drv, &hci->tx_buf_lock, _ps, &sp_flags);
|
|
if (hci->tx_buf_event) {
|
|
_os_event_set(drv, hci->tx_buf_event);
|
|
hci->tx_buf_event = NULL;
|
|
}
|
|
_os_spinunlock(drv, &hci->tx_buf_lock, _ps, &sp_flags);
|
|
}
|
|
#endif /* !RTW_WKARD_SDIO_TX_USE_YIELD */
|
|
#endif /* CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
#endif /* SDIO_TX_THREAD */
|
|
|
|
static void enqueue_rxbuf(struct phl_info_t *phl_info,
|
|
struct phl_queue *pool_list, struct rtw_rx_buf *rxbuf)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
_os_spinlockfg sp_flags;
|
|
|
|
|
|
_os_spinlock(drv, &pool_list->lock, _irq, &sp_flags);
|
|
list_add_tail(&rxbuf->list, &pool_list->queue);
|
|
pool_list->cnt++;
|
|
_os_spinunlock(drv, &pool_list->lock, _irq, &sp_flags);
|
|
}
|
|
|
|
static struct rtw_rx_buf *dequeue_rxbuf(struct phl_info_t *phl_info,
|
|
struct phl_queue *pool_list)
|
|
{
|
|
struct rtw_rx_buf *rxbuf = NULL;
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
_os_spinlockfg sp_flags;
|
|
|
|
|
|
_os_spinlock(drv, &pool_list->lock, _irq, &sp_flags);
|
|
if (!list_empty(&pool_list->queue)) {
|
|
rxbuf = list_first_entry(&pool_list->queue, struct rtw_rx_buf,
|
|
list);
|
|
list_del(&rxbuf->list);
|
|
pool_list->cnt--;
|
|
}
|
|
_os_spinunlock(drv, &pool_list->lock, _irq, &sp_flags);
|
|
|
|
return rxbuf;
|
|
}
|
|
|
|
/*
|
|
* Return RTW_PHL_STATUS_RESOURCE if space in txbuf is not enough for tx_req,
|
|
* or RTW_PHL_STATUS_SUCCESS for tx_req has already been handled and recycled.
|
|
*/
|
|
static enum rtw_phl_status _phl_prepare_tx_sdio(struct phl_info_t *phl_info,
|
|
struct rtw_xmit_req *tx_req,
|
|
struct rtw_tx_buf *txbuf)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
enum rtw_hal_status hstatus;
|
|
void *drv_priv = phl_to_drvpriv(phl_info);
|
|
struct rtw_phl_evt_ops *ops = &phl_info->phl_com->evt_ops;
|
|
u32 align_size = 8;
|
|
u32 wd_len = 48; /* default set to max 48 bytes */
|
|
u32 used_len;
|
|
u32 total_len;
|
|
u8 *tx_buf_data;
|
|
struct rtw_pkt_buf_list *pkt_buf = NULL;
|
|
u8 i = 0;
|
|
|
|
|
|
if (txbuf->agg_cnt == PHL_SDIO_TX_AGG_MAX)
|
|
return RTW_PHL_STATUS_RESOURCE;
|
|
|
|
used_len = _ALIGN(txbuf->used_len, align_size);
|
|
total_len = used_len + wd_len + tx_req->total_len;
|
|
if (total_len > txbuf->buf_len) {
|
|
if (txbuf->agg_cnt)
|
|
return RTW_PHL_STATUS_RESOURCE;
|
|
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: unexpected tx size(%d + %d)!!\n",
|
|
__FUNCTION__, tx_req->total_len, wd_len);
|
|
/* drop, skip this packet */
|
|
goto recycle;
|
|
}
|
|
|
|
tx_buf_data = txbuf->buffer + used_len; /* align start address */
|
|
|
|
hstatus = rtw_hal_fill_txdesc(phl_info->hal, tx_req, tx_buf_data, &wd_len);
|
|
if (hstatus != RTW_HAL_STATUS_SUCCESS) {
|
|
PHL_TRACE(COMP_PHL_DBG|COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: Fail to fill txdesc!(0x%x)\n",
|
|
__FUNCTION__, hstatus);
|
|
/* drop, skip this packet */
|
|
goto recycle;
|
|
}
|
|
|
|
tx_buf_data += wd_len;
|
|
|
|
pkt_buf = (struct rtw_pkt_buf_list *)tx_req->pkt_list;
|
|
for (i = 0; i < tx_req->pkt_cnt; i++, pkt_buf++) {
|
|
_os_mem_cpy(drv_priv, tx_buf_data,
|
|
pkt_buf->vir_addr, pkt_buf->length);
|
|
tx_buf_data += pkt_buf->length;
|
|
}
|
|
|
|
txbuf->used_len = used_len + wd_len + tx_req->total_len;
|
|
txbuf->pkt_len[txbuf->agg_cnt] = tx_req->mdata.pktlen;
|
|
txbuf->wp_offset[txbuf->agg_cnt] = tx_req->mdata.wp_offset;
|
|
txbuf->agg_cnt++;
|
|
if (txbuf->agg_cnt == 1) {
|
|
txbuf->dma_ch = tx_req->mdata.dma_ch;
|
|
} else {
|
|
/* update first packet's txagg_num of wd */
|
|
txbuf->buffer[5] = txbuf->agg_cnt;
|
|
/* Todo: update checksum field */
|
|
}
|
|
|
|
recycle:
|
|
if (RTW_PHL_TREQ_TYPE_TEST_PATTERN == tx_req->treq_type) {
|
|
if (ops->tx_test_recycle) {
|
|
pstatus = ops->tx_test_recycle(phl_info, tx_req);
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: tx_test_recycle fail!! (%d)\n",
|
|
__FUNCTION__, pstatus);
|
|
}
|
|
}
|
|
} else {
|
|
/* RTW_PHL_TREQ_TYPE_NORMAL == tx_req->treq_type */
|
|
if (ops->tx_recycle) {
|
|
pstatus = ops->tx_recycle(drv_priv, tx_req);
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS)
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: tx recycle fail!! (%d)\n",
|
|
__FUNCTION__, pstatus);
|
|
}
|
|
}
|
|
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void _phl_tx_flow_ctrl_sdio(struct phl_info_t *phl_info,
|
|
_os_list *sta_list)
|
|
{
|
|
phl_tx_flow_ctrl(phl_info, sta_list);
|
|
}
|
|
|
|
static enum rtw_phl_status phl_handle_xmit_ring_sdio(
|
|
struct phl_info_t *phl_info,
|
|
struct phl_ring_status *ring_sts)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
struct rtw_phl_tx_ring *tring = ring_sts->ring_ptr;
|
|
struct rtw_xmit_req *tx_req = NULL;
|
|
u16 rptr = 0;
|
|
void *drv_priv = phl_to_drvpriv(phl_info);
|
|
#ifdef SDIO_TX_THREAD
|
|
struct rtw_tx_buf_ring *tx_pool = (struct rtw_tx_buf_ring *)phl_info->hci->txbuf_pool;
|
|
struct rtw_tx_buf *txbuf = NULL;
|
|
#else
|
|
struct rtw_tx_buf *txbuf = (struct rtw_tx_buf *)phl_info->hci->txbuf_pool;
|
|
#endif
|
|
|
|
|
|
do {
|
|
rptr = (u16)_os_atomic_read(drv_priv, &tring->phl_next_idx);
|
|
|
|
tx_req = (struct rtw_xmit_req *)tring->entry[rptr];
|
|
if (!tx_req) {
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: tx_req is NULL!\n", __FUNCTION__);
|
|
pstatus = RTW_PHL_STATUS_FAILURE;
|
|
break;
|
|
}
|
|
tx_req->mdata.macid = ring_sts->macid;
|
|
tx_req->mdata.band = ring_sts->band;
|
|
tx_req->mdata.wmm = ring_sts->wmm;
|
|
tx_req->mdata.hal_port = ring_sts->port;
|
|
/*tx_req->mdata.mbssid = ring_sts->mbssid;*/
|
|
tx_req->mdata.tid = tring->tid;
|
|
tx_req->mdata.dma_ch = tring->dma_ch;
|
|
tx_req->mdata.pktlen = (u16)tx_req->total_len;
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
get_txbuf:
|
|
if (!txbuf) {
|
|
txbuf = alloc_txbuf(phl_info, tx_pool, tring->tid);
|
|
if (!txbuf) {
|
|
pstatus = RTW_PHL_STATUS_RESOURCE;
|
|
break;
|
|
}
|
|
}
|
|
#endif /* SDIO_TX_THREAD */
|
|
pstatus = _phl_prepare_tx_sdio(phl_info, tx_req, txbuf);
|
|
#ifdef SDIO_TX_THREAD
|
|
if (pstatus == RTW_PHL_STATUS_RESOURCE) {
|
|
/* enqueue txbuf */
|
|
enqueue_busy_txbuf(phl_info, tx_pool, txbuf);
|
|
txbuf = NULL;
|
|
goto get_txbuf;
|
|
}
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
/* impossible case, never entered for now */
|
|
PHL_TRACE(COMP_PHL_DBG|COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: prepare tx fail!(%d)\n",
|
|
__FUNCTION__, pstatus);
|
|
break;
|
|
}
|
|
#else /* !SDIO_TX_THREAD */
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
if (pstatus == RTW_PHL_STATUS_RESOURCE) {
|
|
pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
} else {
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: prepare tx fail!(%d)\n",
|
|
__FUNCTION__, pstatus);
|
|
}
|
|
break;
|
|
}
|
|
#endif /* !SDIO_TX_THREAD */
|
|
|
|
_os_atomic_set(drv_priv, &tring->phl_idx, rptr);
|
|
|
|
ring_sts->req_busy--;
|
|
/* TODO: aggregate more packets! */
|
|
if (!ring_sts->req_busy)
|
|
break;
|
|
|
|
if ((rptr + 1) >= MAX_PHL_TX_RING_ENTRY_NUM)
|
|
_os_atomic_set(drv_priv, &tring->phl_next_idx, 0);
|
|
else
|
|
_os_atomic_inc(drv_priv, &tring->phl_next_idx);
|
|
} while (1);
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
if (txbuf)
|
|
enqueue_busy_txbuf(phl_info, tx_pool, txbuf);
|
|
#endif /* SDIO_TX_THREAD */
|
|
phl_release_ring_sts(phl_info, ring_sts);
|
|
|
|
return pstatus;
|
|
}
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
static int phl_tx_sdio_thrd_hdl(void *context)
|
|
{
|
|
struct phl_info_t *phl = context;
|
|
struct hci_info_t *hci = phl->hci;
|
|
struct rtw_tx_buf_ring *tx_pool;
|
|
struct rtw_tx_buf *txbuf = NULL;
|
|
void *drv = phl_to_drvpriv(phl);
|
|
struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl->hal);
|
|
struct bus_cap_t *bus_cap = &hal_com->bus_cap;
|
|
enum rtw_hal_status hstatus;
|
|
u16 retry_time = bus_cap->tx_buf_retry_lmt ? bus_cap->tx_buf_retry_lmt : XMIT_BUFFER_RETRY_LIMIT;
|
|
|
|
#ifdef RTW_XMIT_THREAD_HIGH_PRIORITY
|
|
#ifdef PLATFORM_LINUX
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0))
|
|
sched_set_fifo_low(current);
|
|
#else
|
|
struct sched_param param = { .sched_priority = 1 };
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
#endif
|
|
#endif /* PLATFORM_LINUX */
|
|
#endif /*RTW_XMIT_THREAD_HIGH_PRIORITY*/
|
|
|
|
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_INFO_, "SDIO: tx thread start retry_time=%d\n" ,retry_time);
|
|
|
|
tx_pool = (struct rtw_tx_buf_ring *)hci->txbuf_pool;
|
|
while (1) {
|
|
_os_sema_down(drv, &hci->tx_thrd_sema);
|
|
|
|
check_stop:
|
|
if (_os_thread_check_stop(drv, &hci->tx_thrd))
|
|
break;
|
|
|
|
txbuf = dequeue_busy_txbuf(phl, tx_pool);
|
|
if (!txbuf)
|
|
continue;
|
|
_os_atomic_set(drv, &phl->phl_sw_tx_more, 0);
|
|
hstatus = rtw_hal_sdio_tx(phl->hal, txbuf->dma_ch, txbuf->buffer,
|
|
txbuf->used_len, txbuf->agg_cnt,
|
|
txbuf->pkt_len, txbuf->wp_offset);
|
|
if (hstatus != RTW_HAL_STATUS_SUCCESS) {
|
|
bool overflow;
|
|
|
|
if ((hstatus == RTW_HAL_STATUS_RESOURCE)
|
|
&& (txbuf->retry < retry_time)) {
|
|
txbuf->retry++;
|
|
enqueue_busy_txbuf_to_head(phl, tx_pool, txbuf);
|
|
/* Todo: What to do when TX FIFO not ready? */
|
|
goto check_stop;
|
|
}
|
|
|
|
/* Keep overflow bit(bit31) */
|
|
overflow = (hci->tx_drop_cnt & BIT31) ? true : false;
|
|
hci->tx_drop_cnt++;
|
|
if (overflow)
|
|
hci->tx_drop_cnt |= BIT31;
|
|
|
|
/* Show msg on 2^n times */
|
|
if (!(hci->tx_drop_cnt & (hci->tx_drop_cnt - 1))) {
|
|
PHL_TRACE(COMP_PHL_DBG|COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: drop!(%d) type=%u mgnt=%u len=%u "
|
|
"agg_cnt=%u drop_cnt=%u%s\n",
|
|
__FUNCTION__, hstatus, txbuf->tag, txbuf->mgnt_pkt,
|
|
txbuf->used_len, txbuf->agg_cnt,
|
|
hci->tx_drop_cnt & ~BIT31,
|
|
(hci->tx_drop_cnt & BIT31) ? "(overflow)" : "");
|
|
}
|
|
}
|
|
free_txbuf(phl, tx_pool, txbuf);
|
|
/* if agg thread is waiting tx buffer, notify it */
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
#ifndef RTW_WKARD_SDIO_TX_USE_YIELD
|
|
txbuf_set_ready(phl);
|
|
#endif /* !RTW_WKARD_SDIO_TX_USE_YIELD */
|
|
#else /* CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
phl_schedule_handler(phl->phl_com, &phl->phl_tx_handler);
|
|
#endif /* CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
|
|
/* check stop and if more txbuf for tx */
|
|
goto check_stop;
|
|
}
|
|
|
|
_os_thread_wait_stop(drv, &hci->tx_thrd);
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_INFO_, "SDIO: tx thread down\n");
|
|
|
|
return 0;
|
|
}
|
|
#else /* !SDIO_TX_THREAD */
|
|
static enum rtw_phl_status _phl_tx_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
enum rtw_hal_status hstatus;
|
|
struct hci_info_t *hci = phl_info->hci;
|
|
struct rtw_tx_buf *txbuf = (struct rtw_tx_buf*)hci->txbuf_pool;
|
|
|
|
|
|
if (!txbuf->buffer || !txbuf->buf_len)
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
|
|
hstatus = rtw_hal_sdio_tx(phl_info->hal, txbuf->dma_ch, txbuf->buffer,
|
|
txbuf->used_len, txbuf->agg_cnt,
|
|
txbuf->pkt_len, txbuf->wp_offset);
|
|
txbuf->used_len = 0;
|
|
txbuf->agg_cnt = 0;
|
|
if (hstatus == RTW_HAL_STATUS_SUCCESS)
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
|
|
return RTW_PHL_STATUS_FAILURE;
|
|
}
|
|
#endif /* !SDIO_TX_THREAD */
|
|
|
|
static enum rtw_phl_status phl_tx_check_status_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
|
|
if (PHL_TX_STATUS_STOP_INPROGRESS ==
|
|
_os_atomic_read(phl_to_drvpriv(phl_info), &phl_info->phl_sw_tx_sts)){
|
|
_os_atomic_set(drv, &phl_info->phl_sw_tx_sts, PHL_TX_STATUS_SW_PAUSE);
|
|
pstatus = RTW_PHL_STATUS_FAILURE;
|
|
}
|
|
return pstatus;
|
|
}
|
|
|
|
static void _phl_tx_callback_sdio(void *context)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
|
|
struct rtw_phl_handler *phl_handler;
|
|
struct phl_info_t *phl_info;
|
|
struct phl_ring_status *ring_sts = NULL, *t;
|
|
_os_list sta_list;
|
|
bool tx_pause = false;
|
|
#ifdef SDIO_TX_THREAD
|
|
bool rsrc;
|
|
#endif /* SDIO_TX_THREAD */
|
|
|
|
|
|
phl_handler = (struct rtw_phl_handler *)phl_container_of(context,
|
|
struct rtw_phl_handler,
|
|
os_handler);
|
|
phl_info = (struct phl_info_t *)phl_handler->context;
|
|
|
|
INIT_LIST_HEAD(&sta_list);
|
|
|
|
pstatus = phl_tx_check_status_sdio(phl_info);
|
|
if (pstatus == RTW_PHL_STATUS_FAILURE)
|
|
goto end;
|
|
|
|
/* check datapath sw state */
|
|
tx_pause = phl_datapath_chk_trx_pause(phl_info, PHL_CTRL_TX);
|
|
if (true == tx_pause)
|
|
goto end;
|
|
|
|
#ifdef CONFIG_POWER_SAVE
|
|
/* check ps state when tx is not paused */
|
|
if (false == phl_ps_is_datapath_allowed(phl_info)) {
|
|
PHL_WARN("%s(): datapath is not allowed now... may in low power.\n", __func__);
|
|
goto end;
|
|
}
|
|
#endif
|
|
do {
|
|
if (!phl_check_xmit_ring_resource(phl_info, &sta_list))
|
|
break;
|
|
|
|
/* phl_info->t_fctrl_result would be filled inside phl_tx_flow_ctrl() */
|
|
phl_tx_flow_ctrl(phl_info, &sta_list);
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
rsrc = false; /* default suppose no enough tx resource */
|
|
#endif /* SDIO_TX_THREAD */
|
|
phl_list_for_loop_safe(ring_sts, t, struct phl_ring_status,
|
|
&phl_info->t_fctrl_result, list) {
|
|
list_del(&ring_sts->list);
|
|
|
|
/* ring_sts would be release inside phl_handle_xmit_ring_sdio() */
|
|
pstatus = phl_handle_xmit_ring_sdio(phl_info, ring_sts);
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
if (pstatus != RTW_PHL_STATUS_RESOURCE)
|
|
rsrc = true; /* some tx data has been sent */
|
|
#else /* !SDIO_TX_THREAD */
|
|
pstatus = _phl_tx_sdio(phl_info);
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
u32 drop = phl_info->hci->tx_drop_cnt;
|
|
|
|
/* Keep overflow bit(bit31) */
|
|
phl_info->hci->tx_drop_cnt = (drop & BIT31)
|
|
| (drop + 1);
|
|
drop = phl_info->hci->tx_drop_cnt;
|
|
/* Show msg on 2^n times */
|
|
if (!(drop & (drop - 1))) {
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_ERR_,
|
|
"%s: phl_tx fail!(%d) drop cnt=%u%s\n",
|
|
__FUNCTION__, pstatus,
|
|
drop & ~BIT31,
|
|
drop & BIT31 ? "(overflow)" : "");
|
|
}
|
|
}
|
|
#endif /* !SDIO_TX_THREAD */
|
|
}
|
|
|
|
pstatus = phl_tx_check_status_sdio(phl_info);
|
|
if (pstatus == RTW_PHL_STATUS_FAILURE)
|
|
break;
|
|
|
|
phl_free_deferred_tx_ring(phl_info);
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
/*
|
|
* Break loop when no txbuf for tx data and this function would
|
|
* be schedule again when I/O thread return txbuf.
|
|
*/
|
|
if (!rsrc) {
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
#ifdef RTW_WKARD_SDIO_TX_USE_YIELD
|
|
_os_yield(phl_to_drvpriv(phl_info));
|
|
#else /* !RTW_WKARD_SDIO_TX_USE_YIELD */
|
|
txbuf_wait(phl_info, 1);
|
|
#endif /* !RTW_WKARD_SDIO_TX_USE_YIELD */
|
|
#else /* !CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
break;
|
|
#endif /* !CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
}
|
|
#endif /* SDIO_TX_THREAD */
|
|
} while (1);
|
|
|
|
end:
|
|
phl_free_deferred_tx_ring(phl_info);
|
|
}
|
|
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
static void phl_tx_callback_sdio(void *context)
|
|
{
|
|
struct rtw_phl_handler *phl_handler;
|
|
struct phl_info_t *phl_info;
|
|
void *d;
|
|
|
|
#ifdef RTW_XMIT_THREAD_CB_HIGH_PRIORITY
|
|
#ifdef PLATFORM_LINUX
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0))
|
|
sched_set_fifo_low(current);
|
|
#else
|
|
struct sched_param param = { .sched_priority = 1 };
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
#endif
|
|
#endif /* PLATFORM_LINUX */
|
|
#endif /* RTW_XMIT_THREAD_CB_HIGH_PRIORITY */
|
|
|
|
phl_handler = (struct rtw_phl_handler *)phl_container_of(context,
|
|
struct rtw_phl_handler,
|
|
os_handler);
|
|
phl_info = (struct phl_info_t *)phl_handler->context;
|
|
d = phl_to_drvpriv(phl_info);
|
|
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_INFO_, "SDIO: %s start\n",
|
|
phl_handler->cb_name);
|
|
|
|
while (1) {
|
|
_os_sema_down(d, &(phl_handler->os_handler.os_sema));
|
|
|
|
if (_os_thread_check_stop(d, (_os_thread*)context))
|
|
break;
|
|
|
|
_phl_tx_callback_sdio(context);
|
|
}
|
|
|
|
_os_thread_wait_stop(d, (_os_thread*)context);
|
|
_os_sema_free(d, &(phl_handler->os_handler.os_sema));
|
|
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_INFO_, "SDIO: %s down\n",
|
|
phl_handler->cb_name);
|
|
}
|
|
#else /* !CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
#define phl_tx_callback_sdio _phl_tx_callback_sdio
|
|
#endif /* !CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
|
|
static enum rtw_phl_status phl_prepare_tx_sdio(struct phl_info_t *phl_info,
|
|
struct rtw_xmit_req *tx_req)
|
|
{
|
|
/* not implement yet */
|
|
return RTW_PHL_STATUS_FAILURE;
|
|
}
|
|
|
|
static enum rtw_phl_status phl_tx_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
/* not implement yet */
|
|
return RTW_PHL_STATUS_FAILURE;
|
|
}
|
|
|
|
static enum rtw_phl_status phl_recycle_rx_buf_sdio(struct phl_info_t *phl,
|
|
void *r, u8 ch,
|
|
enum rtw_rx_type type)
|
|
{
|
|
struct hci_info_t *hci_info = (struct hci_info_t *)phl->hci;
|
|
struct rtw_rx_buf_ring *rx_pool = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;
|
|
struct rtw_rx_buf *rxbuf = r;
|
|
void *drv_priv = phl_to_drvpriv(phl);
|
|
|
|
#if 1
|
|
/* Just for debugging, could consider to disable in the future */
|
|
if (!_os_atomic_read(drv_priv, &rxbuf->ref)) {
|
|
PHL_TRACE(COMP_PHL_RECV, _PHL_ERR_,
|
|
"%s: ref count error! (%px)\n", __FUNCTION__, rxbuf);
|
|
_os_warn_on(1);
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
if (!_os_atomic_dec_return(drv_priv, &rxbuf->ref))
|
|
enqueue_rxbuf(phl, &rx_pool->idle_rxbuf_list, rxbuf);
|
|
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
void phl_rx_handle_normal(struct phl_info_t *phl,
|
|
struct rtw_phl_rx_pkt *phl_rx)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
|
|
_os_list frames;
|
|
FUNCIN_WSTS(pstatus);
|
|
|
|
do {
|
|
INIT_LIST_HEAD(&frames);
|
|
|
|
if (phl_rx->r.mdata.rx_rate <= RTW_DATA_RATE_HE_NSS4_MCS11)
|
|
phl->phl_com->phl_stats.rx_rate_nmr[phl_rx->r.mdata.rx_rate]++;
|
|
|
|
pstatus = phl_rx_reorder(phl, phl_rx, &frames);
|
|
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
PHL_TRACE(COMP_PHL_RECV, _PHL_ERR_,
|
|
"%s: phl_rx_reorder"
|
|
" FAIL! (%d)\n",
|
|
__FUNCTION__, pstatus);
|
|
break;
|
|
}
|
|
phl_handle_rx_frame_list(phl, &frames);
|
|
} while (0);
|
|
|
|
FUNCOUT_WSTS(pstatus);
|
|
|
|
}
|
|
|
|
static enum rtw_phl_status phl_rx_sdio(struct phl_info_t *phl)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
enum rtw_hal_status hstatus;
|
|
struct hci_info_t *hci_info = (struct hci_info_t *)phl->hci;
|
|
struct rtw_phl_rx_pkt *phl_rx = NULL;
|
|
struct rtw_rx_buf_ring *rx_pool = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;
|
|
struct rtw_rx_buf *rxbuf = NULL;
|
|
void *drv_priv = phl_to_drvpriv(phl);
|
|
#ifndef CONFIG_PHL_RX_PSTS_PER_PKT
|
|
_os_list frames;
|
|
#endif
|
|
u32 len;
|
|
bool flag = true;
|
|
u8 i;
|
|
u8 mfrag = 0, frag_num = 0;
|
|
u16 netbuf_len = 0;
|
|
|
|
|
|
do {
|
|
if (rxbuf) {
|
|
len = rtw_hal_sdio_parse_rx(phl->hal, rxbuf);
|
|
if (!len) {
|
|
if (!_os_atomic_dec_return(drv_priv, &rxbuf->ref))
|
|
enqueue_rxbuf(phl, &rx_pool->idle_rxbuf_list, rxbuf);
|
|
rxbuf = NULL;
|
|
continue;
|
|
}
|
|
} else {
|
|
rxbuf = dequeue_rxbuf(phl, &rx_pool->pend_rxbuf_list);
|
|
if (!rxbuf) {
|
|
#ifdef CONFIG_PHL_SDIO_READ_RXFF_IN_INT
|
|
rxbuf = dequeue_rxbuf(phl, &rx_pool->busy_rxbuf_list);
|
|
if (!rxbuf)
|
|
break;
|
|
|
|
len = rtw_hal_sdio_parse_rx(phl->hal, rxbuf);
|
|
if (!len) {
|
|
enqueue_rxbuf(phl, &rx_pool->idle_rxbuf_list, rxbuf);
|
|
break;
|
|
}
|
|
#else
|
|
rxbuf = dequeue_rxbuf(phl, &rx_pool->idle_rxbuf_list);
|
|
if (!rxbuf) {
|
|
pstatus = RTW_PHL_STATUS_RESOURCE;
|
|
break;
|
|
}
|
|
|
|
len = rtw_hal_sdio_rx(phl->hal, rxbuf);
|
|
if (!len) {
|
|
enqueue_rxbuf(phl, &rx_pool->idle_rxbuf_list, rxbuf);
|
|
break;
|
|
}
|
|
#endif
|
|
_os_atomic_set(drv_priv, &rxbuf->ref, 1);
|
|
}
|
|
}
|
|
|
|
for (i = rxbuf->agg_start; i < rxbuf->agg_cnt; i++) {
|
|
phl_rx = rtw_phl_query_phl_rx(phl);
|
|
if (!phl_rx) {
|
|
pstatus = RTW_PHL_STATUS_RESOURCE;
|
|
/* No enough resource to handle rx data, */
|
|
/* so maybe take a break */
|
|
rxbuf->agg_start = i;
|
|
enqueue_rxbuf(phl, &rx_pool->pend_rxbuf_list, rxbuf);
|
|
rxbuf = NULL;
|
|
flag = false;
|
|
|
|
#ifdef PHL_RX_BATCH_IND
|
|
_phl_indic_new_rxpkt(phl);
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
phl_rx->type = rxbuf->pkt[i].meta.rpkt_type;
|
|
phl_rx->rxbuf_ptr = (u8*)rxbuf;
|
|
_os_atomic_inc(drv_priv, &rxbuf->ref);
|
|
phl_rx->r.os_priv = NULL;
|
|
_os_mem_cpy(phl->phl_com->drv_priv,
|
|
&phl_rx->r.mdata, &rxbuf->pkt[i].meta,
|
|
sizeof(struct rtw_r_meta_data));
|
|
/*phl_rx->r.shortcut_id;*/
|
|
phl_rx->r.pkt_cnt = 1;
|
|
phl_rx->r.pkt_list->vir_addr = rxbuf->pkt[i].pkt;
|
|
phl_rx->r.pkt_list->length = rxbuf->pkt[i].pkt_len;
|
|
|
|
/* length include WD and packet size */
|
|
len = ((u32)(rxbuf->pkt[i].pkt - rxbuf->pkt[i].wd))
|
|
+ rxbuf->pkt[i].pkt_len;
|
|
hstatus = rtw_hal_handle_rx_buffer(phl->phl_com,
|
|
phl->hal,
|
|
rxbuf->pkt[i].wd,
|
|
len, phl_rx);
|
|
if (hstatus != RTW_HAL_STATUS_SUCCESS) {
|
|
PHL_TRACE(COMP_PHL_RECV, _PHL_ERR_,
|
|
"%s: hal_handle_rx FAIL! (%d)"
|
|
" type=0x%X len=%u\n",
|
|
__FUNCTION__, hstatus,
|
|
phl_rx->type, len);
|
|
}
|
|
|
|
switch (phl_rx->type) {
|
|
case RTW_RX_TYPE_WIFI:
|
|
|
|
#ifdef CONFIG_PHL_SDIO_RX_NETBUF_ALLOC_IN_PHL
|
|
{
|
|
u8 *netbuf = NULL;
|
|
void *drv = phl_to_drvpriv(phl);
|
|
|
|
/* Pre-alloc netbuf and replace pkt_list[0].vir_addr */
|
|
|
|
/* For first fragment packet, driver need allocate 1536 to defrag packet.*/
|
|
mfrag = PHL_GET_80211_HDR_MORE_FRAG(phl_rx->r.pkt_list[0].vir_addr);
|
|
frag_num = PHL_GET_80211_HDR_FRAG_NUM(phl_rx->r.pkt_list[0].vir_addr);
|
|
|
|
if (mfrag == 1 && frag_num == 0) {
|
|
if (phl_rx->r.pkt_list[0].length < RTW_MAX_ETH_PKT_LEN)
|
|
netbuf_len = RTW_MAX_ETH_PKT_LEN;
|
|
else
|
|
netbuf_len = phl_rx->r.pkt_list[0].length;
|
|
} else {
|
|
netbuf_len = phl_rx->r.pkt_list[0].length;
|
|
}
|
|
|
|
netbuf = _os_alloc_netbuf(drv,
|
|
netbuf_len,
|
|
&(phl_rx->r.os_priv));
|
|
|
|
if (netbuf) {
|
|
_os_mem_cpy(drv, netbuf,
|
|
phl_rx->r.pkt_list[0].vir_addr, phl_rx->r.pkt_list[0].length);
|
|
phl_rx->r.pkt_list[0].vir_addr = netbuf;
|
|
|
|
phl_rx->r.os_netbuf_len = netbuf_len;
|
|
phl_rx->rxbuf_ptr = NULL;
|
|
_os_atomic_dec(drv_priv, &rxbuf->ref);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PHL_RX_PSTS_PER_PKT
|
|
if (false == phl_rx_proc_wait_phy_sts(phl, phl_rx)) {
|
|
PHL_TRACE(COMP_PHL_PSTS, _PHL_DEBUG_,
|
|
"phl_rx_proc_wait_phy_sts() return false \n");
|
|
phl_rx_handle_normal(phl, phl_rx);
|
|
} else {
|
|
pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
/*
|
|
* phl_rx already has been took over by
|
|
* phl_rx_reorder(), so clear it here.
|
|
*/
|
|
phl_rx = NULL;
|
|
#else
|
|
INIT_LIST_HEAD(&frames);
|
|
pstatus = phl_rx_reorder(phl, phl_rx, &frames);
|
|
/*
|
|
* phl_rx already has been took over by
|
|
* phl_rx_reorder(), so clear it here.
|
|
*/
|
|
phl_rx = NULL;
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
PHL_TRACE(COMP_PHL_RECV, _PHL_ERR_,
|
|
"%s: phl_rx_reorder"
|
|
" FAIL! (%d)\n",
|
|
__FUNCTION__, pstatus);
|
|
break;
|
|
}
|
|
phl_handle_rx_frame_list(phl, &frames);
|
|
|
|
#endif
|
|
break;
|
|
|
|
case RTW_RX_TYPE_PPDU_STATUS:
|
|
phl_rx_proc_ppdu_sts(phl, phl_rx);
|
|
#ifdef CONFIG_PHL_RX_PSTS_PER_PKT
|
|
phl_rx_proc_phy_sts(phl, phl_rx);
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (phl_rx) {
|
|
_os_atomic_dec(drv_priv, &rxbuf->ref);
|
|
phl_release_phl_rx(phl, phl_rx);
|
|
phl_rx = NULL;
|
|
}
|
|
}
|
|
|
|
if (rxbuf) {
|
|
if (rxbuf->next_ptr) {
|
|
rxbuf->len -= (u32)(rxbuf->next_ptr - rxbuf->ptr);
|
|
rxbuf->ptr = rxbuf->next_ptr;
|
|
rxbuf->next_ptr = NULL;
|
|
continue;
|
|
}
|
|
if (!_os_atomic_dec_return(drv_priv, &rxbuf->ref))
|
|
enqueue_rxbuf(phl, &rx_pool->idle_rxbuf_list, rxbuf);
|
|
rxbuf = NULL;
|
|
|
|
#ifdef PHL_RX_BATCH_IND
|
|
if (phl->rx_new_pending)
|
|
_phl_indic_new_rxpkt(phl);
|
|
#endif
|
|
}
|
|
} while (flag);
|
|
|
|
return pstatus;
|
|
}
|
|
|
|
static void phl_rx_stop_sdio(struct phl_info_t *phl)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl);
|
|
|
|
_os_atomic_set(drv, &phl->phl_sw_rx_sts, PHL_RX_STATUS_SW_PAUSE);
|
|
}
|
|
|
|
static void _phl_rx_callback_sdio(void *context)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
|
|
struct rtw_phl_handler *phl_handler;
|
|
struct phl_info_t *phl_info;
|
|
bool rx_pause = false;
|
|
|
|
phl_handler = (struct rtw_phl_handler *)phl_container_of(context,
|
|
struct rtw_phl_handler,
|
|
os_handler);
|
|
phl_info = (struct phl_info_t *)phl_handler->context;
|
|
|
|
/* check datapath sw state */
|
|
rx_pause = phl_datapath_chk_trx_pause(phl_info, PHL_CTRL_RX);
|
|
if (true == rx_pause)
|
|
goto end;
|
|
|
|
if (false == phl_check_recv_ring_resource(phl_info))
|
|
goto chk_stop;
|
|
|
|
pstatus = phl_rx_sdio(phl_info);
|
|
if (pstatus == RTW_PHL_STATUS_RESOURCE) {
|
|
PHL_TRACE(COMP_PHL_RECV, _PHL_WARNING_,
|
|
"%s: resource starvation!\n", __FUNCTION__);
|
|
} else if (pstatus != RTW_PHL_STATUS_SUCCESS) {
|
|
PHL_TRACE(COMP_PHL_RECV, _PHL_ERR_,
|
|
"%s: phl_rx fail!(%d)\n", __FUNCTION__, pstatus);
|
|
}
|
|
|
|
chk_stop:
|
|
if (PHL_RX_STATUS_STOP_INPROGRESS ==
|
|
_os_atomic_read(phl_to_drvpriv(phl_info), &phl_info->phl_sw_rx_sts))
|
|
phl_rx_stop_sdio(phl_info);
|
|
end:
|
|
/* enable rx interrupt*/
|
|
rtw_hal_config_interrupt(phl_info->hal , RTW_PHL_RESUME_RX_INT);
|
|
}
|
|
|
|
static void phl_rx_callback_sdio(void *context)
|
|
{
|
|
#ifdef CONFIG_PHL_SDIO_RX_CB_THREAD
|
|
#ifdef RTW_RECV_THREAD_HIGH_PRIORITY
|
|
#ifdef PLATFORM_LINUX
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0))
|
|
sched_set_fifo_low(current);
|
|
#else
|
|
struct sched_param param = { .sched_priority = 1 };
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
#endif
|
|
#endif /* PLATFORM_LINUX */
|
|
#endif /*RTW_RECV_THREAD_HIGH_PRIORITY*/
|
|
struct rtw_phl_handler *phl_handler;
|
|
struct phl_info_t *phl_info;
|
|
void *d;
|
|
|
|
phl_handler = (struct rtw_phl_handler *)phl_container_of(context,
|
|
struct rtw_phl_handler,
|
|
os_handler);
|
|
phl_info = (struct phl_info_t *)phl_handler->context;
|
|
d = phl_to_drvpriv(phl_info);
|
|
|
|
while (1) {
|
|
_os_sema_down(d, &(phl_handler->os_handler.os_sema));
|
|
|
|
if (_os_thread_check_stop(d, (_os_thread*)context))
|
|
break;
|
|
|
|
_phl_rx_callback_sdio(context);
|
|
}
|
|
|
|
_os_thread_wait_stop(d, (_os_thread*)context);
|
|
_os_sema_free(d, &(phl_handler->os_handler.os_sema));
|
|
return;
|
|
#else
|
|
_phl_rx_callback_sdio(context);
|
|
#endif
|
|
}
|
|
|
|
static enum rtw_phl_status phl_register_trx_hdlr_sdio(struct phl_info_t *phl)
|
|
{
|
|
struct rtw_phl_handler *tx_handler = &phl->phl_tx_handler;
|
|
struct rtw_phl_handler *rx_handler = &phl->phl_rx_handler;
|
|
void *drv = phl_to_drvpriv(phl);
|
|
enum rtw_phl_status pstatus;
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
const char *tx_hdl_cb_name = "RTW_TX_CB_THREAD";
|
|
#endif
|
|
|
|
#ifdef CONFIG_PHL_SDIO_RX_CB_THREAD
|
|
const char *rx_hdl_cb_name = "RTW_RX_CB_THREAD";
|
|
#endif
|
|
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
tx_handler->type = RTW_PHL_HANDLER_PRIO_NORMAL;
|
|
_os_strncpy(tx_handler->cb_name, tx_hdl_cb_name,
|
|
(strlen(tx_hdl_cb_name) > RTW_PHL_HANDLER_CB_NAME_LEN) ?
|
|
RTW_PHL_HANDLER_CB_NAME_LEN : strlen(tx_hdl_cb_name));
|
|
#else
|
|
tx_handler->type = RTW_PHL_HANDLER_PRIO_LOW;
|
|
#endif
|
|
tx_handler->callback = phl_tx_callback_sdio;
|
|
tx_handler->context = phl;
|
|
tx_handler->drv_priv = drv;
|
|
pstatus = phl_register_handler(phl->phl_com, tx_handler);
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS)
|
|
return pstatus;
|
|
|
|
#ifdef CONFIG_PHL_SDIO_RX_CB_THREAD
|
|
rx_handler->type = RTW_PHL_HANDLER_PRIO_NORMAL;
|
|
_os_strncpy(rx_handler->cb_name, rx_hdl_cb_name,
|
|
(strlen(rx_hdl_cb_name) > RTW_PHL_HANDLER_CB_NAME_LEN) ?
|
|
RTW_PHL_HANDLER_CB_NAME_LEN : strlen(rx_hdl_cb_name));
|
|
#else
|
|
rx_handler->type = RTW_PHL_HANDLER_PRIO_LOW;
|
|
#endif
|
|
rx_handler->callback = phl_rx_callback_sdio;
|
|
rx_handler->context = phl;
|
|
rx_handler->drv_priv = drv;
|
|
pstatus = phl_register_handler(phl->phl_com, rx_handler);
|
|
if (pstatus != RTW_PHL_STATUS_SUCCESS)
|
|
return pstatus;
|
|
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
|
|
#ifdef CONFIG_PHL_SDIO_READ_RXFF_IN_INT
|
|
static enum rtw_phl_status phl_recv_rxfifo_sdio(struct phl_info_t *phl)
|
|
{
|
|
struct hci_info_t *hci_info = (struct hci_info_t *)phl->hci;
|
|
struct rtw_rx_buf_ring *rx_pool = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;
|
|
struct rtw_rx_buf *rxbuf = NULL;
|
|
u32 len;
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
|
|
do {
|
|
rxbuf = dequeue_rxbuf(phl, &rx_pool->idle_rxbuf_list);
|
|
if (!rxbuf) {
|
|
pstatus = RTW_PHL_STATUS_RESOURCE;
|
|
break;
|
|
}
|
|
|
|
len = rtw_hal_sdio_rx(phl->hal, rxbuf);
|
|
if (!len) {
|
|
enqueue_rxbuf(phl, &rx_pool->idle_rxbuf_list, rxbuf);
|
|
break;
|
|
}
|
|
|
|
enqueue_rxbuf(phl, &rx_pool->busy_rxbuf_list, rxbuf);
|
|
rtw_phl_start_rx_process(phl);
|
|
} while (1);
|
|
|
|
return pstatus;
|
|
}
|
|
#endif
|
|
|
|
static void phl_trx_deinit_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
struct hci_info_t *hci = phl_info->hci;
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
struct rtw_rx_buf_ring *rx_pool;
|
|
struct rtw_rx_buf *rxbuf;
|
|
struct rtw_tx_buf *txbuf;
|
|
#ifdef SDIO_TX_THREAD
|
|
struct rtw_tx_buf_ring *tx_pool;
|
|
u32 i;
|
|
#endif
|
|
|
|
|
|
FUNCIN();
|
|
|
|
/* TODO: stop RX callback */
|
|
/* TODO: stop TX callback */
|
|
|
|
/* freee RX resource */
|
|
if (hci->rxbuf_pool) {
|
|
rx_pool = (struct rtw_rx_buf_ring *)hci->rxbuf_pool;
|
|
|
|
while (rx_pool->idle_rxbuf_list.cnt) {
|
|
rxbuf = dequeue_rxbuf(phl_info, &rx_pool->idle_rxbuf_list);
|
|
if (!rxbuf)
|
|
break;
|
|
_os_kmem_free(drv, rxbuf->buffer, rxbuf->buf_len);
|
|
}
|
|
|
|
while (rx_pool->pend_rxbuf_list.cnt) {
|
|
rxbuf = dequeue_rxbuf(phl_info, &rx_pool->pend_rxbuf_list);
|
|
if (!rxbuf)
|
|
break;
|
|
_os_kmem_free(drv, rxbuf->buffer, rxbuf->buf_len);
|
|
}
|
|
|
|
while (rx_pool->busy_rxbuf_list.cnt) {
|
|
rxbuf = dequeue_rxbuf(phl_info, &rx_pool->busy_rxbuf_list);
|
|
if (!rxbuf)
|
|
break;
|
|
_os_kmem_free(drv, rxbuf->buffer, rxbuf->buf_len);
|
|
}
|
|
|
|
pq_deinit(drv, &rx_pool->idle_rxbuf_list);
|
|
pq_deinit(drv, &rx_pool->busy_rxbuf_list);
|
|
pq_deinit(drv, &rx_pool->pend_rxbuf_list);
|
|
|
|
_os_mem_free(drv, rx_pool->rxbufblock,
|
|
rx_pool->total_blocks_size);
|
|
_os_mem_free(drv, hci->rxbuf_pool,
|
|
sizeof(struct rtw_rx_buf_ring));
|
|
hci->rxbuf_pool = NULL;
|
|
}
|
|
|
|
/* freee TX resource */
|
|
#ifdef SDIO_TX_THREAD
|
|
_os_thread_stop(drv, &hci->tx_thrd);
|
|
phl_tx_sdio_wake_thrd(phl_info);
|
|
_os_thread_deinit(drv, &hci->tx_thrd);
|
|
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
#ifndef RTW_WKARD_SDIO_TX_USE_YIELD
|
|
_os_spinlock_free(drv, &hci->tx_buf_lock);
|
|
#endif /* !RTW_WKARD_SDIO_TX_USE_YIELD */
|
|
#endif /* CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
|
|
if (hci->txbuf_pool) {
|
|
tx_pool = (struct rtw_tx_buf_ring *)hci->txbuf_pool;
|
|
hci->txbuf_pool = NULL;
|
|
|
|
txbuf = (struct rtw_tx_buf *)tx_pool->txbufblock;
|
|
for (i = 0; i < tx_pool->block_cnt_alloc; i++) {
|
|
list_del(&txbuf->list);
|
|
_os_kmem_free(drv, txbuf->buffer, txbuf->buf_len);
|
|
txbuf->buffer = NULL;
|
|
txbuf->buf_len = 0;
|
|
txbuf++;
|
|
}
|
|
|
|
pq_deinit(drv, &tx_pool->idle_list);
|
|
pq_deinit(drv, &tx_pool->busy_list);
|
|
pq_deinit(drv, &tx_pool->mgnt_idle_list);
|
|
pq_deinit(drv, &tx_pool->mgnt_busy_list);
|
|
|
|
_os_mem_free(drv, tx_pool->txbufblock,
|
|
tx_pool->total_blocks_size);
|
|
_os_mem_free(drv, tx_pool,
|
|
sizeof(struct rtw_tx_buf_ring));
|
|
}
|
|
#else /* !SDIO_TX_THREAD */
|
|
if (hci->txbuf_pool) {
|
|
txbuf = (struct rtw_tx_buf*)hci->txbuf_pool;
|
|
hci->txbuf_pool = NULL;
|
|
if (txbuf->buffer)
|
|
_os_kmem_free(drv, txbuf->buffer, txbuf->buf_len);
|
|
_os_mem_free(drv, txbuf, sizeof(struct rtw_tx_buf));
|
|
}
|
|
#endif /* !SDIO_TX_THREAD */
|
|
if (hci->tx_drop_cnt) {
|
|
PHL_TRACE(COMP_PHL_XMIT, _PHL_WARNING_,
|
|
"%s: tx_drop_cnt=%u%s\n", __FUNCTION__,
|
|
hci->tx_drop_cnt & ~BIT31,
|
|
hci->tx_drop_cnt & BIT31 ? "(overflow)" : "");
|
|
hci->tx_drop_cnt = 0;
|
|
}
|
|
|
|
FUNCOUT();
|
|
}
|
|
|
|
static enum rtw_phl_status phl_trx_init_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
|
|
struct hci_info_t *hci = phl_info->hci;
|
|
struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
|
|
struct bus_cap_t *bus_cap = &hal_com->bus_cap;
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
#ifdef SDIO_TX_THREAD
|
|
struct rtw_tx_buf_ring *tx_pool;
|
|
#endif
|
|
struct rtw_tx_buf *txbuf;
|
|
struct rtw_rx_buf_ring *rx_pool;
|
|
struct rtw_rx_buf *rxbuf;
|
|
u32 i;
|
|
|
|
|
|
FUNCIN_WSTS(pstatus);
|
|
PHL_TRACE(COMP_PHL_XMIT|COMP_PHL_DBG, _PHL_DEBUG_,
|
|
"%s: tx_buf_num(%u)\n", __FUNCTION__, bus_cap->tx_buf_num);
|
|
PHL_TRACE(COMP_PHL_XMIT|COMP_PHL_DBG, _PHL_DEBUG_,
|
|
"%s: tx_buf_size(%u)\n", __FUNCTION__, bus_cap->tx_buf_size);
|
|
PHL_TRACE(COMP_PHL_XMIT|COMP_PHL_DBG, _PHL_DEBUG_,
|
|
"%s: mgnt_buf_num(%u)\n", __FUNCTION__, bus_cap->tx_mgnt_buf_num);
|
|
PHL_TRACE(COMP_PHL_XMIT|COMP_PHL_DBG, _PHL_DEBUG_,
|
|
"%s: mgnt_buf_size(%u)\n", __FUNCTION__, bus_cap->tx_mgnt_buf_size);
|
|
PHL_TRACE(COMP_PHL_RECV|COMP_PHL_DBG, _PHL_DEBUG_,
|
|
"%s: rx_buf_num(%u)\n", __FUNCTION__, bus_cap->rx_buf_num);
|
|
PHL_TRACE(COMP_PHL_RECV|COMP_PHL_DBG, _PHL_DEBUG_,
|
|
"%s: rx_buf_size(%u)\n", __FUNCTION__, bus_cap->rx_buf_size);
|
|
|
|
do {
|
|
#ifdef SDIO_TX_THREAD
|
|
hci->txbuf_pool = _os_mem_alloc(drv,
|
|
sizeof(struct rtw_tx_buf_ring));
|
|
if (!hci->txbuf_pool)
|
|
break;
|
|
tx_pool = (struct rtw_tx_buf_ring*)hci->txbuf_pool;
|
|
tx_pool->block_cnt_alloc = bus_cap->tx_mgnt_buf_num
|
|
+ bus_cap->tx_buf_num;
|
|
tx_pool->total_blocks_size = tx_pool->block_cnt_alloc
|
|
* sizeof(struct rtw_tx_buf);
|
|
tx_pool->txbufblock = _os_mem_alloc(drv,
|
|
tx_pool->total_blocks_size);
|
|
if (!tx_pool->txbufblock)
|
|
break;
|
|
|
|
pq_init(drv, &tx_pool->idle_list);
|
|
pq_init(drv, &tx_pool->busy_list);
|
|
pq_init(drv, &tx_pool->mgnt_idle_list);
|
|
pq_init(drv, &tx_pool->mgnt_busy_list);
|
|
|
|
txbuf = (struct rtw_tx_buf *)tx_pool->txbufblock;
|
|
for (i = 0; i < bus_cap->tx_mgnt_buf_num; i++, txbuf++) {
|
|
txbuf->tag = RTW_PHL_PKT_TYPE_MGNT;
|
|
txbuf->buf_len = bus_cap->tx_mgnt_buf_size;
|
|
txbuf->buffer = _os_kmem_alloc(drv, txbuf->buf_len);
|
|
if (!txbuf->buffer)
|
|
break;
|
|
INIT_LIST_HEAD(&txbuf->list);
|
|
enqueue_txbuf(phl_info, &tx_pool->mgnt_idle_list, txbuf, _tail);
|
|
}
|
|
if (i != bus_cap->tx_mgnt_buf_num)
|
|
break;
|
|
for (; i < tx_pool->block_cnt_alloc; i++, txbuf++) {
|
|
txbuf->tag = RTW_PHL_PKT_TYPE_DATA;
|
|
txbuf->buf_len = bus_cap->tx_buf_size;
|
|
txbuf->buffer = _os_kmem_alloc(drv, txbuf->buf_len);
|
|
if (!txbuf->buffer)
|
|
break;
|
|
INIT_LIST_HEAD(&txbuf->list);
|
|
enqueue_txbuf(phl_info, &tx_pool->idle_list, txbuf, _tail);
|
|
}
|
|
if (i != tx_pool->block_cnt_alloc)
|
|
break;
|
|
|
|
#ifdef CONFIG_PHL_SDIO_TX_CB_THREAD
|
|
#ifndef RTW_WKARD_SDIO_TX_USE_YIELD
|
|
_os_spinlock_init(drv, &hci->tx_buf_lock);
|
|
#endif /* !RTW_WKARD_SDIO_TX_USE_YIELD */
|
|
#endif /* CONFIG_PHL_SDIO_TX_CB_THREAD */
|
|
#else /* !SDIO_TX_THREAD */
|
|
hci->txbuf_pool = _os_mem_alloc(drv, sizeof(struct rtw_tx_buf));
|
|
if (!hci->txbuf_pool)
|
|
break;
|
|
txbuf = (struct rtw_tx_buf*)hci->txbuf_pool;
|
|
txbuf->buf_len = bus_cap->tx_buf_size;
|
|
txbuf->buffer = _os_kmem_alloc(drv, txbuf->buf_len);
|
|
#endif /* !SDIO_TX_THREAD */
|
|
|
|
hci->rxbuf_pool = _os_mem_alloc(drv,
|
|
sizeof(struct rtw_rx_buf_ring));
|
|
if (!hci->rxbuf_pool)
|
|
break;
|
|
rx_pool = (struct rtw_rx_buf_ring*)hci->rxbuf_pool;
|
|
rx_pool->block_cnt_alloc = bus_cap->rx_buf_num;
|
|
rx_pool->total_blocks_size = rx_pool->block_cnt_alloc
|
|
* sizeof(struct rtw_rx_buf);
|
|
rx_pool->rxbufblock = _os_mem_alloc(drv,
|
|
rx_pool->total_blocks_size);
|
|
if (!rx_pool->rxbufblock)
|
|
break;
|
|
|
|
pq_init(drv, &rx_pool->idle_rxbuf_list);
|
|
pq_init(drv, &rx_pool->busy_rxbuf_list);
|
|
pq_init(drv, &rx_pool->pend_rxbuf_list);
|
|
|
|
rxbuf = (struct rtw_rx_buf *)rx_pool->rxbufblock;
|
|
for (i = 0; i < rx_pool->block_cnt_alloc; i++) {
|
|
rxbuf->buf_len = bus_cap->rx_buf_size;
|
|
rxbuf->buffer = _os_kmem_alloc(drv, rxbuf->buf_len);
|
|
if (!rxbuf->buffer)
|
|
break;
|
|
|
|
INIT_LIST_HEAD(&rxbuf->list);
|
|
enqueue_rxbuf(phl_info, &rx_pool->idle_rxbuf_list,
|
|
rxbuf);
|
|
|
|
rxbuf++;
|
|
}
|
|
if (i != rx_pool->block_cnt_alloc)
|
|
break;
|
|
|
|
pstatus = phl_register_trx_hdlr_sdio(phl_info);
|
|
|
|
#ifdef SDIO_TX_THREAD
|
|
_os_sema_init(drv, &hci->tx_thrd_sema, 0);
|
|
_os_thread_init(drv, &hci->tx_thrd, phl_tx_sdio_thrd_hdl,
|
|
phl_info, "rtw_sdio_tx");
|
|
_os_thread_schedule(drv, &hci->tx_thrd);
|
|
#endif
|
|
} while (false);
|
|
|
|
if (RTW_PHL_STATUS_SUCCESS != pstatus)
|
|
phl_trx_deinit_sdio(phl_info);
|
|
|
|
FUNCOUT_WSTS(pstatus);
|
|
|
|
return pstatus;
|
|
}
|
|
|
|
static u8 _rx_buf_size_kb(struct phl_info_t *phl)
|
|
{
|
|
struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl->hal);
|
|
struct bus_cap_t *bus_cap = &hal_com->bus_cap;
|
|
u32 size;
|
|
|
|
|
|
size = bus_cap->rx_buf_size >> 10; /* change unit to KB */
|
|
return (u8)((size > 0xFF) ? 0xFF : size);
|
|
}
|
|
|
|
static enum rtw_phl_status phl_trx_cfg_sdio(struct phl_info_t *phl)
|
|
{
|
|
/*
|
|
* Default TX setting.
|
|
* Include following setting:
|
|
* a. Release tx size limitation of (32K-4) bytes.
|
|
*/
|
|
rtw_hal_sdio_tx_cfg(phl->hal);
|
|
|
|
/*
|
|
* default RX agg setting form mac team,
|
|
* timeout threshold 32us, size threshold is depended on rx buffer size.
|
|
* RX agg setting still need to be optimized by IC and real case.
|
|
*/
|
|
rtw_hal_sdio_rx_agg_cfg(phl->hal, true, 1, 32, _rx_buf_size_kb(phl), 0);
|
|
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void phl_trx_stop_sdio(struct phl_info_t *phl)
|
|
{
|
|
}
|
|
|
|
#define _PLTFM_TX_TIMEOUT 50 /* unit: ms */
|
|
static enum rtw_phl_status phl_pltfm_tx_sdio(struct phl_info_t *phl, void *pkt)
|
|
{
|
|
struct rtw_h2c_pkt *h2c_pkt = (struct rtw_h2c_pkt *)pkt;
|
|
u8 dma_ch;
|
|
u32 start;
|
|
enum rtw_hal_status res;
|
|
|
|
|
|
dma_ch = rtw_hal_get_fwcmd_queue_idx(phl->hal);
|
|
|
|
start = _os_get_cur_time_ms();
|
|
do {
|
|
res = rtw_hal_sdio_tx(phl->hal, dma_ch, h2c_pkt->vir_head,
|
|
h2c_pkt->data_len, 1, NULL, NULL);
|
|
if (res == RTW_HAL_STATUS_RESOURCE) {
|
|
if (phl_get_passing_time_ms(start) < _PLTFM_TX_TIMEOUT)
|
|
continue;
|
|
|
|
PHL_TRACE(COMP_PHL_XMIT|COMP_PHL_DBG, _PHL_ERR_,
|
|
"%s: pltfm_tx timeout(> %u ms)!\n",
|
|
__FUNCTION__, _PLTFM_TX_TIMEOUT);
|
|
}
|
|
break;
|
|
} while (1);
|
|
|
|
phl_enqueue_idle_h2c_pkt(phl, h2c_pkt);
|
|
if (res == RTW_HAL_STATUS_SUCCESS)
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
|
|
PHL_TRACE(COMP_PHL_XMIT|COMP_PHL_DBG, _PHL_ERR_,
|
|
"%s: pltfm_tx fail!(0x%x)\n", __FUNCTION__, res);
|
|
return RTW_PHL_STATUS_FAILURE;
|
|
}
|
|
|
|
static void phl_free_h2c_pkt_buf_sdio(struct phl_info_t *phl_info,
|
|
struct rtw_h2c_pkt *h2c_pkt)
|
|
{
|
|
if (!h2c_pkt->vir_head || !h2c_pkt->buf_len)
|
|
return;
|
|
|
|
_os_kmem_free(phl_to_drvpriv(phl_info),
|
|
h2c_pkt->vir_head, h2c_pkt->buf_len);
|
|
h2c_pkt->vir_head = NULL;
|
|
h2c_pkt->buf_len = 0;
|
|
}
|
|
|
|
static enum rtw_phl_status phl_alloc_h2c_pkt_buf_sdio(
|
|
struct phl_info_t *phl_info,
|
|
struct rtw_h2c_pkt *h2c_pkt, u32 buf_len)
|
|
{
|
|
void *buf = NULL;
|
|
|
|
|
|
buf = _os_kmem_alloc(phl_to_drvpriv(phl_info), buf_len);
|
|
if (!buf)
|
|
return RTW_PHL_STATUS_FAILURE;
|
|
|
|
h2c_pkt->vir_head = buf;
|
|
h2c_pkt->buf_len = buf_len;
|
|
|
|
return RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void phl_trx_reset_sdio(struct phl_info_t *phl, u8 type)
|
|
{
|
|
struct rtw_phl_com_t *phl_com = phl->phl_com;
|
|
struct rtw_stats *phl_stats = &phl_com->phl_stats;
|
|
|
|
if (PHL_CTRL_TX & type) {
|
|
phl_reset_tx_stats(phl_stats);
|
|
}
|
|
|
|
if (PHL_CTRL_RX & type) {
|
|
phl_reset_rx_stats(phl_stats);
|
|
}
|
|
}
|
|
|
|
static void phl_tx_resume_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
|
|
_os_atomic_set(drv, &phl_info->phl_sw_tx_sts, PHL_TX_STATUS_RUNNING);
|
|
}
|
|
|
|
static void phl_rx_resume_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl_info);
|
|
|
|
_os_atomic_set(drv, &phl_info->phl_sw_rx_sts, PHL_RX_STATUS_RUNNING);
|
|
}
|
|
|
|
static void phl_trx_resume_sdio(struct phl_info_t *phl, u8 type)
|
|
{
|
|
if (PHL_CTRL_TX & type)
|
|
phl_tx_resume_sdio(phl);
|
|
|
|
if (PHL_CTRL_RX & type)
|
|
phl_rx_resume_sdio(phl);
|
|
}
|
|
|
|
static void phl_req_tx_stop_sdio(struct phl_info_t *phl)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl);
|
|
|
|
_os_atomic_set(drv, &phl->phl_sw_tx_sts,
|
|
PHL_TX_STATUS_STOP_INPROGRESS);
|
|
}
|
|
|
|
static void phl_req_rx_stop_sdio(struct phl_info_t *phl)
|
|
{
|
|
void *drv = phl_to_drvpriv(phl);
|
|
|
|
_os_atomic_set(drv, &phl->phl_sw_rx_sts,
|
|
PHL_RX_STATUS_STOP_INPROGRESS);
|
|
}
|
|
|
|
static bool phl_is_tx_pause_sdio(struct phl_info_t *phl)
|
|
{
|
|
void *drvpriv = phl_to_drvpriv(phl);
|
|
|
|
if (PHL_TX_STATUS_SW_PAUSE == _os_atomic_read(drvpriv,
|
|
&phl->phl_sw_tx_sts))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static bool phl_is_rx_pause_sdio(struct phl_info_t *phl)
|
|
{
|
|
void *drvpriv = phl_to_drvpriv(phl);
|
|
|
|
if (PHL_RX_STATUS_SW_PAUSE == _os_atomic_read(drvpriv,
|
|
&phl->phl_sw_rx_sts)) {
|
|
if (true == rtw_phl_is_phl_rx_idle(phl))
|
|
return true;
|
|
else
|
|
return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void *phl_get_txbd_buf_sdio(struct phl_info_t *phl)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void *phl_get_rxbd_buf_sdio(struct phl_info_t *phl)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void phl_recycle_rx_pkt_sdio(struct phl_info_t *phl_info,
|
|
struct rtw_phl_rx_pkt *phl_rx)
|
|
{
|
|
|
|
if (phl_rx->r.os_priv)
|
|
_os_free_netbuf(phl_to_drvpriv(phl_info),
|
|
phl_rx->r.pkt_list[0].vir_addr,
|
|
phl_rx->r.os_netbuf_len,
|
|
phl_rx->r.os_priv);
|
|
|
|
phl_recycle_rx_buf(phl_info, phl_rx);
|
|
}
|
|
|
|
|
|
void phl_tx_watchdog_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
|
|
}
|
|
|
|
static struct phl_hci_trx_ops ops_sdio = {
|
|
.hci_trx_init = phl_trx_init_sdio,
|
|
.hci_trx_deinit = phl_trx_deinit_sdio,
|
|
|
|
.prepare_tx = phl_prepare_tx_sdio,
|
|
.recycle_rx_buf = phl_recycle_rx_buf_sdio,
|
|
.tx = phl_tx_sdio,
|
|
.rx = phl_rx_sdio,
|
|
.trx_cfg = phl_trx_cfg_sdio,
|
|
.trx_stop = phl_trx_stop_sdio,
|
|
.pltfm_tx = phl_pltfm_tx_sdio,
|
|
.free_h2c_pkt_buf = phl_free_h2c_pkt_buf_sdio,
|
|
.alloc_h2c_pkt_buf = phl_alloc_h2c_pkt_buf_sdio,
|
|
.trx_reset = phl_trx_reset_sdio,
|
|
.trx_resume = phl_trx_resume_sdio,
|
|
.req_tx_stop = phl_req_tx_stop_sdio,
|
|
.req_rx_stop = phl_req_rx_stop_sdio,
|
|
.is_tx_pause = phl_is_tx_pause_sdio,
|
|
.is_rx_pause = phl_is_rx_pause_sdio,
|
|
.get_txbd_buf = phl_get_txbd_buf_sdio,
|
|
.get_rxbd_buf = phl_get_rxbd_buf_sdio,
|
|
.recycle_rx_pkt = phl_recycle_rx_pkt_sdio,
|
|
.register_trx_hdlr = phl_register_trx_hdlr_sdio,
|
|
.rx_handle_normal = phl_rx_handle_normal,
|
|
.tx_watchdog = phl_tx_watchdog_sdio,
|
|
#ifdef CONFIG_PHL_SDIO_READ_RXFF_IN_INT
|
|
.recv_rxfifo = phl_recv_rxfifo_sdio,
|
|
#endif
|
|
};
|
|
|
|
enum rtw_phl_status phl_hook_trx_ops_sdio(struct phl_info_t *phl_info)
|
|
{
|
|
enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
|
|
|
|
if (NULL != phl_info) {
|
|
phl_info->hci_trx_ops = &ops_sdio;
|
|
pstatus = RTW_PHL_STATUS_SUCCESS;
|
|
}
|
|
|
|
return pstatus;
|
|
}
|