blob: f97e2573e623a442d0bbac8a69f87f90d75ee496 [file] [log] [blame]
/**
* rwnx_utils.c
*
* IPC utility function definitions
*
* Copyright (C) RivieraWaves 2012-2021
*/
#include "rwnx_utils.h"
#include "rwnx_defs.h"
#include "rwnx_rx.h"
#include "rwnx_tx.h"
#include "rwnx_msg_rx.h"
#include "rwnx_debugfs.h"
#include "rwnx_prof.h"
#include "ipc_host.h"
#ifdef CONFIG_RWNX_FULLMAC
#define FW_STR "fmac"
#endif
/**
* rwnx_ipc_buf_pool_alloc() - Allocate and push to fw a pool of IPC buffer.
*
* @rwnx_hw: Main driver structure
* @pool: Pool to allocate
* @nb: Size of the pool to allocate
* @buf_size: Size of one pool element
* @pool_name: Name of the pool
* @push: Function to push one pool buffer to fw
*
* This function will allocate an array to store the list of IPC buffers,
* a dma pool and @nb element in the dma pool.
* Each buffer is initialized with '0' and then pushed to fw using the @push function.
*
* Return: 0 on success and <0 upon error. If error is returned any allocated
* memory is NOT freed and rwnx_ipc_buf_pool_dealloc() must be called.
*/
static int rwnx_ipc_buf_pool_alloc(struct rwnx_hw *rwnx_hw,
struct rwnx_ipc_buf_pool *pool,
int nb, size_t buf_size, char *pool_name,
int (*push)(struct ipc_host_env_tag *,
struct rwnx_ipc_buf *))
{
struct rwnx_ipc_buf *buf;
int i;
pool->nb = 0;
/* allocate buf array */
pool->buffers = kmalloc(nb * sizeof(struct rwnx_ipc_buf), GFP_KERNEL);
if (!pool->buffers) {
dev_err(rwnx_hw->dev, "Allocation of buffer array for %s failed\n",
pool_name);
return -ENOMEM;
}
/* allocate dma pool */
pool->pool = dma_pool_create(pool_name, rwnx_hw->dev, buf_size,
cache_line_size(), 0);
if (!pool->pool) {
dev_err(rwnx_hw->dev, "Allocation of dma pool %s failed\n",
pool_name);
return -ENOMEM;
}
for (i = 0, buf = pool->buffers; i < nb; buf++, i++) {
/* allocate a buffer */
buf->size = buf_size;
buf->addr = dma_pool_alloc(pool->pool, GFP_KERNEL, &buf->dma_addr);
if (!buf->addr) {
dev_err(rwnx_hw->dev, "Allocation of block %d/%d in %s failed\n",
(i + 1), nb, pool_name);
return -ENOMEM;
}
pool->nb++;
/* reset the buffer */
memset(buf->addr, 0, buf_size);
/* push it to FW */
push(rwnx_hw->ipc_env, buf);
}
return 0;
}
/**
* rwnx_ipc_buf_pool_dealloc() - Free all memory allocated for a pool
*
* @pool: Pool to free
*
* Must be call once after rwnx_ipc_buf_pool_alloc(), even if it returned
* an error
*/
static void rwnx_ipc_buf_pool_dealloc(struct rwnx_ipc_buf_pool *pool)
{
struct rwnx_ipc_buf *buf;
int i;
for (i = 0, buf = pool->buffers; i < pool->nb ; buf++, i++) {
dma_pool_free(pool->pool, buf->addr, buf->dma_addr);
}
pool->nb = 0;
if (pool->pool)
dma_pool_destroy(pool->pool);
pool->pool = NULL;
if (pool->buffers)
kfree(pool->buffers);
pool->buffers = NULL;
}
/**
* rwnx_ipc_buf_alloc - Alloc a single ipc buffer and MAP it for DMA access
*
* @rwnx_hw: Main driver structure
* @buf: IPC buffer to allocate
* @buf_size: Size of the buffer to allocate
* @dir: DMA direction
* @init: Pointer to initial data to write in buffer before DMA sync. Used
* only if direction is DMA_TO_DEVICE and it must be at least @buf_size long
*
* It allocates a buffer, initializes it if @init is set, and map it for DMA
* Use @rwnx_ipc_buf_dealloc when this buffer is no longer needed.
*
* @return: 0 on success and <0 upon error. If error is returned any allocated
* memory has been freed.
*/
int rwnx_ipc_buf_alloc(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf,
size_t buf_size, enum dma_data_direction dir, const void *init)
{
buf->addr = kmalloc(buf_size, GFP_KERNEL);
if (!buf->addr)
return -ENOMEM;
buf->size = buf_size;
if ((dir == DMA_TO_DEVICE) && init) {
memcpy(buf->addr, init, buf_size);
}
buf->dma_addr = dma_map_single(rwnx_hw->dev, buf->addr, buf_size, dir);
if (dma_mapping_error(rwnx_hw->dev, buf->dma_addr)) {
kfree(buf->addr);
buf->addr = NULL;
return -EIO;
}
return 0;
}
/**
* rwnx_ipc_buf_dealloc() - Free memory allocated for a single ipc buffer
*
* @rwnx_hw: Main driver structure
* @buf: IPC buffer to free
*
* IPC buffer must have been allocated by @rwnx_ipc_buf_alloc() or initialized
* by @rwnx_ipc_buf_init() and pointing to a buffer allocated by kmalloc.
*/
void rwnx_ipc_buf_dealloc(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf)
{
if (!buf->addr)
return;
dma_unmap_single(rwnx_hw->dev, buf->dma_addr, buf->size, DMA_TO_DEVICE);
kfree(buf->addr);
buf->addr = NULL;
}
/**
* rwnx_ipc_buf_a2e_init - Initialize an Application to Embedded IPC buffer
* with a pre-allocated buffer
*
* @rwnx_hw: Main driver structure
* @buf: IPC buffer to initialize
* @data: Data buffer to use for the IPC buffer.
* @buf_size: Size of the buffer the @data buffer
*
* Initialize the IPC buffer with the provided buffer and map it for DMA transfer.
* The mapping direction is always DMA_TO_DEVICE as this an "a2e" buffer.
* Use @rwnx_ipc_buf_dealloc() when this buffer is no longer needed.
*
* @return: 0 on success and <0 upon error. If error is returned the @data buffer
* is freed.
*/
int rwnx_ipc_buf_a2e_init(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf,
void *data, size_t buf_size)
{
buf->addr = data;
buf->size = buf_size;
buf->dma_addr = dma_map_single(rwnx_hw->dev, buf->addr, buf_size,
DMA_TO_DEVICE);
if (dma_mapping_error(rwnx_hw->dev, buf->dma_addr)) {
buf->addr = NULL;
return -EIO;
}
return 0;
}
/**
* rwnx_ipc_buf_release() - Release DMA mapping for an IPC buffer
*
* @rwnx_hw: Main driver structure
* @buf: IPC buffer to release
* @dir: DMA direction.
*
* This also "release" the IPC buffer structure (i.e. its addr field is reset)
* so that it cannot be re-used except to map another buffer.
*/
void rwnx_ipc_buf_release(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf,
enum dma_data_direction dir)
{
if (!buf->addr)
return;
dma_unmap_single(rwnx_hw->dev, buf->dma_addr, buf->size, dir);
buf->addr = NULL;
}
/**
* rwnx_ipc_buf_e2a_sync() - Synchronize all (or part) of an IPC buffer before
* reading content written by the embedded
*
* @rwnx_hw: Main driver structure
* @buf: IPC buffer to sync
* @len: Length to read, 0 means the whole buffer
*/
void rwnx_ipc_buf_e2a_sync(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf,
size_t len)
{
if (!len)
len = buf->size;
dma_sync_single_for_cpu(rwnx_hw->dev, buf->dma_addr, len, DMA_FROM_DEVICE);
}
/**
* rwnx_ipc_buf_e2a_sync_back() - Synchronize back all (or part) of an IPC buffer
* to allow embedded updating its content.
*
* @rwnx_hw: Main driver structure
* @buf: IPC buffer to sync
* @len: Length to sync back, 0 means the whole buffer
*
* Must be called after each call to rwnx_ipc_buf_e2a_sync() even if host didn't
* modified the content of the buffer.
*/
void rwnx_ipc_buf_e2a_sync_back(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf,
size_t len)
{
if (!len)
len = buf->size;
dma_sync_single_for_device(rwnx_hw->dev, buf->dma_addr, len, DMA_FROM_DEVICE);
}
/**
* rwnx_ipc_rxskb_alloc() - Allocate a skb for RX path
*
* @rwnx_hw: Main driver data
* @buf: rwnx_ipc_buf structure to store skb address
* @skb_size: Size of the buffer to allocate
*
* Allocate a skb for RX path, meaning that the data buffer is written by the firmware
* and needs then to be DMA mapped.
*
* Note that even though the result is stored in a struct rwnx_ipc_buf, in this case the
* rwnx_ipc_buf.addr points to skb structure whereas the rwnx_ipc_buf.dma_addr is the
* DMA address of the skb data buffer (i.e. skb->data)
*/
static int rwnx_ipc_rxskb_alloc(struct rwnx_hw *rwnx_hw,
struct rwnx_ipc_buf *buf, size_t skb_size)
{
struct sk_buff *skb = dev_alloc_skb(skb_size);
if (unlikely(!skb)) {
dev_err(rwnx_hw->dev, "Allocation of RX skb failed\n");
buf->addr = NULL;
return -ENOMEM;
}
buf->dma_addr = dma_map_single(rwnx_hw->dev, skb->data, skb_size,
DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(rwnx_hw->dev, buf->dma_addr))) {
dev_err(rwnx_hw->dev, "DMA mapping of RX skb failed\n");
dev_kfree_skb(skb);
buf->addr = NULL;
return -EIO;
}
buf->addr = skb;
buf->size = skb_size;
return 0;
}
/**
* rwnx_ipc_rxskb_reset_pattern() - Reset pattern in a RX skb or unsupported
* RX vector buffer
*
* @rwnx_hw: Main driver data
* @buf: RX skb to reset
* @pattern_offset: Pattern location, in byte from the start of the buffer
*
* Reset the pattern in a RX/unsupported RX vector skb buffer to inform embedded
* that it has been processed by the host.
* Pattern in a 32bit value.
*/
static void rwnx_ipc_rxskb_reset_pattern(struct rwnx_hw *rwnx_hw,
struct rwnx_ipc_buf *buf,
size_t pattern_offset)
{
struct sk_buff *skb = buf->addr;
u32 *pattern = (u32 *)(skb->data + pattern_offset);
*pattern = 0;
*(u32 *)(skb->data) = 0; // aic
dma_sync_single_for_device(rwnx_hw->dev, buf->dma_addr + pattern_offset,
sizeof(u32), DMA_FROM_DEVICE);
}
/**
* rwnx_ipc_rxskb_dealloc() - Free a skb allocated for the RX path
*
* @rwnx_hw: Main driver data
* @buf: Rx skb to free
*
* Free a RX skb allocated by @rwnx_ipc_rxskb_alloc
*/
static void rwnx_ipc_rxskb_dealloc(struct rwnx_hw *rwnx_hw,
struct rwnx_ipc_buf *buf)
{
if (!buf->addr)
return;
dma_unmap_single(rwnx_hw->dev, buf->dma_addr, buf->size, DMA_TO_DEVICE);
dev_kfree_skb((struct sk_buff *)buf->addr);
buf->addr = NULL;
}
/**
* rwnx_ipc_unsup_rx_vec_elem_allocs() - Allocate and push an unsupported
* RX vector buffer for the FW
*
* @rwnx_hw: Main driver data
* @elem: Pointer to the skb elem that will contain the address of the buffer
*/
int rwnx_ipc_unsuprxvec_alloc(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf)
{
int err;
err = rwnx_ipc_rxskb_alloc(rwnx_hw, buf, rwnx_hw->ipc_env->unsuprxvec_sz);
if (err)
return err;
rwnx_ipc_rxskb_reset_pattern(rwnx_hw, buf,
offsetof(struct rx_vector_desc, pattern));
ipc_host_unsuprxvec_push(rwnx_hw->ipc_env, buf);
return 0;
}
/**
* rwnx_ipc_unsuprxvec_repush() - Reset and repush an already allocated buffer
* for unsupported RX vector
*
* @rwnx_hw: Main driver data
* @buf: Buf to repush
*/
void rwnx_ipc_unsuprxvec_repush(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf)
{
rwnx_ipc_rxskb_reset_pattern(rwnx_hw, buf,
offsetof(struct rx_vector_desc, pattern));
ipc_host_unsuprxvec_push(rwnx_hw->ipc_env, buf);
}
/**
* rwnx_ipc_unsuprxvecs_alloc() - Allocate and push all unsupported RX
* vector buffers for the FW
*
* @rwnx_hw: Main driver data
*/
static int rwnx_ipc_unsuprxvecs_alloc(struct rwnx_hw *rwnx_hw)
{
struct rwnx_ipc_buf *buf;
int i;
memset(rwnx_hw->unsuprxvecs, 0, sizeof(rwnx_hw->unsuprxvecs));
for (i = 0, buf = rwnx_hw->unsuprxvecs; i < ARRAY_SIZE(rwnx_hw->unsuprxvecs); i++, buf++)
{
if (rwnx_ipc_unsuprxvec_alloc(rwnx_hw, buf)) {
dev_err(rwnx_hw->dev, "Failed to allocate unsuprxvec buf %d\n", i + 1);
return -ENOMEM;
}
}
return 0;
}
/**
* rwnx_ipc_unsuprxvecs_dealloc() - Free all unsupported RX vector buffers
* allocated for the FW
*
* @rwnx_hw: Main driver data
*/
static void rwnx_ipc_unsuprxvecs_dealloc(struct rwnx_hw *rwnx_hw)
{
struct rwnx_ipc_buf *buf;
int i;
for (i = 0, buf = rwnx_hw->unsuprxvecs; i < ARRAY_SIZE(rwnx_hw->unsuprxvecs); i++, buf++)
{
rwnx_ipc_rxskb_dealloc(rwnx_hw, buf);
}
}
/**
* rwnx_ipc_rxbuf_alloc() - Allocate and push a rx buffer for the FW
*
* @rwnx_hw: Main driver data
* @buf: IPC buffer where to store address of the skb. In fullmac this
* parameter is not available so look for the first free IPC buffer
*/
int rwnx_ipc_rxbuf_alloc(struct rwnx_hw *rwnx_hw)
{
int err;
struct rwnx_ipc_buf *buf;
int nb = 0, idx;
spin_lock_bh(&rwnx_hw->rxbuf_lock);
idx = rwnx_hw->rxbuf_idx;
while (rwnx_hw->rxbufs[idx].addr && (nb < RWNX_RXBUFF_MAX)) {
printk("w %d %p\n", idx, rwnx_hw->rxbufs[idx].addr);
idx = ( idx + 1 ) % RWNX_RXBUFF_MAX;
nb++;
}
if (nb == RWNX_RXBUFF_MAX) {
dev_err(rwnx_hw->dev, "No more free space for rxbuff");
printk("No more free space for rxbuff %d %d %d\n", rwnx_hw->rxbuf_idx, atomic_read(&rwnx_hw->rxbuf_cnt), rwnx_hw->ipc_env->rxbuf_idx);
spin_unlock_bh(&rwnx_hw->rxbuf_lock);
return -ENOMEM;
}
buf = &rwnx_hw->rxbufs[idx];
//printk("alloc %d\n", idx);
err = rwnx_ipc_rxskb_alloc(rwnx_hw, buf, rwnx_hw->ipc_env->rxbuf_sz);
if (err){
printk("ipc_rxskb_alloc fail %d %d %d %d\n", rwnx_hw->rxbuf_idx, atomic_read(&rwnx_hw->rxbuf_cnt), rwnx_hw->ipc_env->rxbuf_idx, err);
spin_unlock_bh(&rwnx_hw->rxbuf_lock);
return err;
}
/* Save idx so that on next push the free slot will be found quicker */
rwnx_hw->rxbuf_idx = ( idx + 1 ) % RWNX_RXBUFF_MAX;
atomic_inc(&rwnx_hw->rxbuf_cnt);
rwnx_ipc_rxskb_reset_pattern(rwnx_hw, buf, offsetof(struct hw_rxhdr, pattern));
RWNX_RXBUFF_HOSTID_SET(buf, RWNX_RXBUFF_IDX_TO_HOSTID(idx));
ipc_host_rxbuf_push(rwnx_hw->ipc_env, buf);
spin_unlock_bh(&rwnx_hw->rxbuf_lock);
return 0;
}
/**
* rwnx_ipc_rxbuf_dealloc() - Free a RX buffer for the FW
*
* @rwnx_hw: Main driver data
* @buf: IPC buffer associated to the RX buffer to free
*/
void rwnx_ipc_rxbuf_dealloc(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf)
{
rwnx_ipc_rxskb_dealloc(rwnx_hw, buf);
}
/**
* rwnx_ipc_rxbuf_repush() - Reset and repush an already allocated RX buffer
*
* @rwnx_hw: Main driver data
* @buf: Buf to repush
*
* In case a skb is not forwarded to upper layer it can be re-used.
*/
void rwnx_ipc_rxbuf_repush(struct rwnx_hw *rwnx_hw, struct rwnx_ipc_buf *buf)
{
rwnx_ipc_rxskb_reset_pattern(rwnx_hw, buf, offsetof(struct hw_rxhdr, pattern));
ipc_host_rxbuf_push(rwnx_hw->ipc_env, buf);
}
/**
* rwnx_ipc_rxbufs_alloc() - Allocate and push all RX buffer for the FW
*
* @rwnx_hw: Main driver data
*/
static int rwnx_ipc_rxbufs_alloc(struct rwnx_hw *rwnx_hw)
{
int i, nb = rwnx_hw->ipc_env->rxbuf_nb;
memset(rwnx_hw->rxbufs, 0, sizeof(rwnx_hw->rxbufs));
for (i = 0; i < nb; i++) {
if (rwnx_ipc_rxbuf_alloc(rwnx_hw)) {
dev_err(rwnx_hw->dev, "Failed to allocate rx buf %d/%d\n",
i + 1, nb);
return -ENOMEM;
}
}
return 0;
}
/**
* rwnx_ipc_rxbufs_dealloc() - Free all RX buffer allocated for the FW
*
* @rwnx_hw: Main driver data
*/
static void rwnx_ipc_rxbufs_dealloc(struct rwnx_hw *rwnx_hw)
{
struct rwnx_ipc_buf *buf;
int i;
for (i = 0, buf = rwnx_hw->rxbufs; i < ARRAY_SIZE(rwnx_hw->rxbufs); i++, buf++) {
rwnx_ipc_rxskb_dealloc(rwnx_hw, buf);
}
}
/**
* rwnx_ipc_rxdesc_repush() - Repush a RX descriptor to FW
*
* @rwnx_hw: Main driver data
* @buf: RX descriptor to repush
*
* Once RX buffer has been received, the RX descriptor used by FW to upload this
* buffer can be re-used for another RX buffer.
*/
void rwnx_ipc_rxdesc_repush(struct rwnx_hw *rwnx_hw,
struct rwnx_ipc_buf *buf)
{
struct rxdesc_tag *rxdesc = buf->addr;
rxdesc->status = 0;
dma_sync_single_for_device(rwnx_hw->dev, buf->dma_addr,
sizeof(struct rxdesc_tag), DMA_BIDIRECTIONAL);
ipc_host_rxdesc_push(rwnx_hw->ipc_env, buf);
}
/**
* rwnx_ipc_rxbuf_from_hostid() - Return IPC buffer of a RX buffer from a hostid
*
* @rwnx_hw: Main driver data
* @hostid: Hostid of the RX buffer
* @return: Pointer to the RX buffer with the provided hostid and NULL if the
* hostid is invalid or no buffer is associated.
*/
struct rwnx_ipc_buf *rwnx_ipc_rxbuf_from_hostid(struct rwnx_hw *rwnx_hw, u32 hostid)
{
int rxbuf_idx = RWNX_RXBUFF_HOSTID_TO_IDX(hostid);
if (RWNX_RXBUFF_VALID_IDX(rxbuf_idx)) {
struct rwnx_ipc_buf *buf = &rwnx_hw->rxbufs[rxbuf_idx];
if (buf->addr && (RWNX_RXBUFF_HOSTID_GET(buf) == hostid))
return buf;
dev_err(rwnx_hw->dev, "Invalid Rx buff: hostid=%d addr=%p hostid_in_buff=%d\n",
hostid, buf->addr, (buf->addr) ? RWNX_RXBUFF_HOSTID_GET(buf): -1);
if (buf->addr)
rwnx_ipc_rxbuf_dealloc(rwnx_hw, buf);
}
dev_err(rwnx_hw->dev, "RX Buff invalid hostid [%d]\n", hostid);
return NULL;
}
/**
* rwnx_elems_deallocs() - Deallocate IPC storage elements.
* @rwnx_hw: Main driver data
*
* This function deallocates all the elements required for communications with
* LMAC, such as Rx Data elements, MSGs elements, ...
* This function should be called in correspondence with the allocation function.
*/
static void rwnx_elems_deallocs(struct rwnx_hw *rwnx_hw)
{
printk("rwnx_elems_deallocs 1\n");
rwnx_ipc_rxbufs_dealloc(rwnx_hw);
rwnx_ipc_unsuprxvecs_dealloc(rwnx_hw);
#ifdef CONFIG_RWNX_FULLMAC
rwnx_ipc_buf_pool_dealloc(&rwnx_hw->rxdesc_pool);
#endif
rwnx_ipc_buf_pool_dealloc(&rwnx_hw->msgbuf_pool);
rwnx_ipc_buf_pool_dealloc(&rwnx_hw->dbgbuf_pool);
rwnx_ipc_buf_pool_dealloc(&rwnx_hw->radar_pool);
rwnx_ipc_buf_pool_dealloc(&rwnx_hw->txcfm_pool);
rwnx_ipc_buf_dealloc(rwnx_hw, &rwnx_hw->tx_pattern);
rwnx_ipc_buf_dealloc(rwnx_hw, &rwnx_hw->dbgdump.buf);
printk("rwnx_elems_deallocs end\n");
}
/**
* rwnx_elems_allocs() - Allocate IPC storage elements.
* @rwnx_hw: Main driver data
*
* This function allocates all the elements required for communications with
* LMAC, such as Rx Data elements, MSGs elements, ...
* This function should be called in correspondence with the deallocation function.
*/
static int rwnx_elems_allocs(struct rwnx_hw *rwnx_hw)
{
RWNX_DBG(RWNX_FN_ENTRY_STR);
if (dma_set_coherent_mask(rwnx_hw->dev, DMA_BIT_MASK(32)))
goto err_alloc;
if (rwnx_ipc_buf_pool_alloc(rwnx_hw, &rwnx_hw->msgbuf_pool,
IPC_MSGE2A_BUF_CNT,
sizeof(struct ipc_e2a_msg),
"rwnx_ipc_msgbuf_pool",
ipc_host_msgbuf_push))
goto err_alloc;
if (rwnx_ipc_buf_pool_alloc(rwnx_hw, &rwnx_hw->dbgbuf_pool,
IPC_DBGBUF_CNT,
sizeof(struct ipc_dbg_msg),
"rwnx_ipc_dbgbuf_pool",
ipc_host_dbgbuf_push))
goto err_alloc;
if (rwnx_ipc_buf_pool_alloc(rwnx_hw, &rwnx_hw->radar_pool,
IPC_RADARBUF_CNT,
sizeof(struct radar_pulse_array_desc),
"rwnx_ipc_radar_pool",
ipc_host_radar_push))
goto err_alloc;
if (rwnx_ipc_unsuprxvecs_alloc(rwnx_hw))
goto err_alloc;
if (rwnx_ipc_buf_a2e_alloc(rwnx_hw, &rwnx_hw->tx_pattern, sizeof(u32),
&rwnx_tx_pattern))
goto err_alloc;
ipc_host_pattern_push(rwnx_hw->ipc_env, &rwnx_hw->tx_pattern);
#if 0
printk("%s: 8\n", __func__);
if (rwnx_ipc_buf_e2a_alloc(rwnx_hw, &rwnx_hw->dbgdump.buf,
sizeof(struct dbg_debug_dump_tag)))
goto err_alloc;
printk("%s: 9\n", __func__);
ipc_host_dbginfo_push(rwnx_hw->ipc_env, &rwnx_hw->dbgdump.buf);
/*
* Note that the RX buffers are no longer allocated here as their size depends on the
* FW configuration, which is not available at that time.
* They will be allocated when checking the parameter compatibility between the driver
* and the underlying components (i.e. during the rwnx_handle_dynparams() execution)
*/
printk("%s: 10\n", __func__);
#endif
#ifdef CONFIG_RWNX_FULLMAC
if (rwnx_ipc_buf_pool_alloc(rwnx_hw, &rwnx_hw->rxdesc_pool,
rwnx_hw->ipc_env->rxdesc_nb,
sizeof(struct rxdesc_tag),
"rwnx_ipc_rxdesc_pool",
ipc_host_rxdesc_push))
goto err_alloc;
#endif /* CONFIG_RWNX_FULLMAC */
return 0;
err_alloc:
dev_err(rwnx_hw->dev, "Error while allocating IPC buffers\n");
rwnx_elems_deallocs(rwnx_hw);
return -ENOMEM;
}
/**
* rwnx_ipc_msg_push() - Push a msg to IPC queue
*
* @rwnx_hw: Main driver data
* @msg_buf: Pointer to message
* @len: Size, in bytes, of message
*/
void rwnx_ipc_msg_push(struct rwnx_hw *rwnx_hw, void *msg_buf, uint16_t len)
{
ipc_host_msg_push(rwnx_hw->ipc_env, msg_buf, len);
}
/**
* rwnx_ipc_txdesc_push() - Push a txdesc to FW
*
* @rwnx_hw: Main driver data
* @sw_txhdr: Pointer to the SW TX header associated to the descriptor to push
* @skb: TX Buffer associated. Pointer saved in ipc env to retrieve it upon confirmation.
* @hw_queue: Hw queue to push txdesc to
*/
#if 0
void rwnx_ipc_txdesc_push(struct rwnx_hw *rwnx_hw, struct rwnx_sw_txhdr *sw_txhdr,
struct sk_buff *skb, int hw_queue)
{
struct txdesc_host *txdesc_host = &sw_txhdr->desc;
struct rwnx_ipc_buf *ipc_desc = &sw_txhdr->ipc_desc;
txdesc_host->ctrl.hwq = hw_queue;
txdesc_host->api.host.hostid = ipc_host_tx_host_ptr_to_id(rwnx_hw->ipc_env, skb);
txdesc_host->ready = 0xFFFFFFFF;
if (!txdesc_host->api.host.hostid) {
dev_err(rwnx_hw->dev, "No more tx_hostid available \n");
return;
}
if (rwnx_ipc_buf_a2e_init(rwnx_hw, ipc_desc, txdesc_host, sizeof(*txdesc_host)))
return ;
ipc_host_txdesc_push(rwnx_hw->ipc_env, ipc_desc);
}
#endif
/**
* rwnx_ipc_get_skb_from_cfm() - Retrieve the TX buffer associated to a confirmation buffer
*
* @rwnx_hw: Main driver data
* @buf: IPC buffer for the confirmation buffer
* @return: Pointer to TX buffer associated to this confirmation and NULL if confirmation
* has not yet been updated by firmware
*
* To ensure that a confirmation has been processed by firmware check if the hostid field
* has been updated. If this is the case retrieve TX buffer from it and reset it, otherwise
* simply return NULL.
*/
struct sk_buff *rwnx_ipc_get_skb_from_cfm(struct rwnx_hw *rwnx_hw,
struct rwnx_ipc_buf *buf)
{
struct sk_buff *skb = NULL;
struct tx_cfm_tag *cfm = buf->addr;
/* get ownership of confirmation */
rwnx_ipc_buf_e2a_sync(rwnx_hw, buf, 0);
/* Check host id in the confirmation. */
/* If 0 it means that this confirmation has not yet been updated by firmware */
if (cfm->hostid) {
skb = ipc_host_tx_host_id_to_ptr(rwnx_hw->ipc_env, cfm->hostid);
if (unlikely(!skb)) {
dev_err(rwnx_hw->dev, "Cannot retrieve skb from cfm=%p/0x%llx, hostid %d in confirmation\n",
buf->addr, buf->dma_addr, cfm->hostid);
} else {
/* Unmap TX descriptor */
struct rwnx_ipc_buf *ipc_desc = &((struct rwnx_txhdr *)skb->data)->sw_hdr->ipc_desc;
rwnx_ipc_buf_a2e_release(rwnx_hw, ipc_desc);
}
cfm->hostid = 0;
}
/* always re-give ownership to firmware. */
rwnx_ipc_buf_e2a_sync_back(rwnx_hw, buf, 0);
return skb;
}
/**
* rwnx_ipc_sta_buffer_init - Initialize counter of buffered data for a given sta
*
* @rwnx_hw: Main driver data
* @sta_idx: Index of the station to initialize
*/
void rwnx_ipc_sta_buffer_init(struct rwnx_hw *rwnx_hw, int sta_idx)
{
int i;
volatile u32_l *buffered;
if (sta_idx >= NX_REMOTE_STA_MAX)
return;
buffered = rwnx_hw->ipc_env->shared->buffered[sta_idx];
for (i = 0; i < TID_MAX; i++) {
*buffered++ = 0;
}
}
/**
* rwnx_ipc_sta_buffer - Update counter of buffered data for a given sta
*
* @rwnx_hw: Main driver data
* @sta: Managed station
* @tid: TID on which data has been added or removed
* @size: Size of data to add (or remove if < 0) to STA buffer.
*/
void rwnx_ipc_sta_buffer(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta, int tid, int size)
{
u32_l *buffered;
if (!sta)
return;
if ((sta->sta_idx >= NX_REMOTE_STA_MAX) || (tid >= TID_MAX))
return;
buffered = &rwnx_hw->ipc_env->shared->buffered[sta->sta_idx][tid];
if (size < 0) {
size = -size;
if (*buffered < size)
*buffered = 0;
else
*buffered -= size;
} else {
// no test on overflow
*buffered += size;
}
}
/**
* rwnx_msgind() - IRQ handler callback for %IPC_IRQ_E2A_MSG
*
* @pthis: Pointer to main driver data
* @arg: Pointer to IPC buffer from msgbuf_pool
*/
static u8 rwnx_msgind(void *pthis, void *arg)
{
struct rwnx_hw *rwnx_hw = pthis;
struct rwnx_ipc_buf *buf = arg;
struct ipc_e2a_msg *msg = buf->addr;
u8 ret = 0;
REG_SW_SET_PROFILING(rwnx_hw, SW_PROF_MSGIND);
/* Look for pattern which means that this hostbuf has been used for a MSG */
if (msg->pattern != IPC_MSGE2A_VALID_PATTERN) {
ret = -1;
goto msg_no_push;
}
/* Relay further actions to the msg parser */
rwnx_rx_handle_msg(rwnx_hw, msg);
/* Reset the msg buffer and re-use it */
msg->pattern = 0;
wmb();
/* Push back the buffer to the LMAC */
ipc_host_msgbuf_push(rwnx_hw->ipc_env, buf);
msg_no_push:
REG_SW_CLEAR_PROFILING(rwnx_hw, SW_PROF_MSGIND);
return ret;
}
/**
* rwnx_msgackind() - IRQ handler callback for %IPC_IRQ_E2A_MSG_ACK
*
* @pthis: Pointer to main driver data
* @hostid: Pointer to command acknowledged
*/
static u8 rwnx_msgackind(void *pthis, void *hostid)
{
struct rwnx_hw *rwnx_hw = (struct rwnx_hw *)pthis;
rwnx_hw->cmd_mgr->llind(rwnx_hw->cmd_mgr, (struct rwnx_cmd *)hostid);
return -1;
}
/**
* rwnx_radarind() - IRQ handler callback for %IPC_IRQ_E2A_RADAR
*
* @pthis: Pointer to main driver data
* @arg: Pointer to IPC buffer from radar_pool
*/
static u8 rwnx_radarind(void *pthis, void *arg)
{
#ifdef CONFIG_RWNX_RADAR
struct rwnx_hw *rwnx_hw = pthis;
struct rwnx_ipc_buf *buf = arg;
struct radar_pulse_array_desc *pulses = buf->addr;
u8 ret = 0;
int i;
/* Look for pulse count meaning that this hostbuf contains RADAR pulses */
if (pulses->cnt == 0) {
ret = -1;
goto radar_no_push;
}
if (rwnx_radar_detection_is_enable(&rwnx_hw->radar, pulses->idx)) {
/* Save the received pulses only if radar detection is enabled */
for (i = 0; i < pulses->cnt; i++) {
struct rwnx_radar_pulses *p = &rwnx_hw->radar.pulses[pulses->idx];
p->buffer[p->index] = pulses->pulse[i];
p->index = (p->index + 1) % RWNX_RADAR_PULSE_MAX;
if (p->count < RWNX_RADAR_PULSE_MAX)
p->count++;
}
/* Defer pulse processing in separate work */
if (! work_pending(&rwnx_hw->radar.detection_work))
schedule_work(&rwnx_hw->radar.detection_work);
}
/* Reset the radar bufent and re-use it */
pulses->cnt = 0;
wmb();
/* Push back the buffer to the LMAC */
ipc_host_radar_push(rwnx_hw->ipc_env, buf);
radar_no_push:
return ret;
#else
return -1;
#endif
}
/**
* rwnx_prim_tbtt_ind() - IRQ handler callback for %IPC_IRQ_E2A_TBTT_PRIM
*
* @pthis: Pointer to main driver data
*/
#if 0
static void rwnx_prim_tbtt_ind(void *pthis)
{
#if 0
struct rwnx_hw *rwnx_hw = (struct rwnx_hw *)pthis;
rwnx_tx_bcns(rwnx_hw);
#endif
}
#endif
/**
* rwnx_sec_tbtt_ind() - IRQ handler callback for %IPC_IRQ_E2A_TBTT_SEC
*
* @pthis: Pointer to main driver data
*/
#if 0
static void rwnx_sec_tbtt_ind(void *pthis)
{
}
#endif
/**
* rwnx_dbgind() - IRQ handler callback for %IPC_IRQ_E2A_DBG
*
* @pthis: Pointer to main driver data
* @hostid: Pointer to IPC buffer from dbgbuf_pool
*/
static u8 rwnx_dbgind(void *pthis, void *arg)
{
struct rwnx_hw *rwnx_hw = (struct rwnx_hw *)pthis;
struct rwnx_ipc_buf *buf = arg;
struct ipc_dbg_msg *dbg_msg = buf->addr;
u8 ret = 0;
REG_SW_SET_PROFILING(rwnx_hw, SW_PROF_DBGIND);
/* Look for pattern which means that this hostbuf has been used for a MSG */
if (dbg_msg->pattern != IPC_DBG_VALID_PATTERN) {
ret = -1;
goto dbg_no_push;
}
/* Display the string */
printk("%s %s", (char *)FW_STR, (char *)dbg_msg->string);
/* Reset the msg buffer and re-use it */
dbg_msg->pattern = 0;
wmb();
/* Push back the buffer to the LMAC */
ipc_host_dbgbuf_push(rwnx_hw->ipc_env, buf);
dbg_no_push:
REG_SW_CLEAR_PROFILING(rwnx_hw, SW_PROF_DBGIND);
return ret;
}
/**
* rwnx_ipc_rxbuf_init() - Allocate and initialize RX buffers.
*
* @rwnx_hw: Main driver data
* @rxbuf_sz: Size of the buffer to be allocated
*
* This function updates the RX buffer size according to the parameter and allocates the
* RX buffers
*/
int rwnx_ipc_rxbuf_init(struct rwnx_hw *rwnx_hw, uint32_t rxbuf_sz)
{
rwnx_hw->ipc_env->rxbuf_sz = rxbuf_sz;
return rwnx_ipc_rxbufs_alloc(rwnx_hw);
}
/**
* rwnx_ipc_init() - Initialize IPC interface.
*
* @rwnx_hw: Main driver data
* @shared_ram: Pointer to shared memory that contains IPC shared struct
*
* This function initializes IPC interface by registering callbacks, setting
* shared memory area and calling IPC Init function.
* It should be called only once during driver's lifetime.
*/
int rwnx_ipc_init(struct rwnx_hw *rwnx_hw, u8 *shared_ram)
{
struct ipc_host_cb_tag cb;
int res;
RWNX_DBG(RWNX_FN_ENTRY_STR);
/* initialize the API interface */
cb.recv_data_ind = rwnx_rxdataind;
cb.recv_radar_ind = rwnx_radarind;
cb.recv_msg_ind = rwnx_msgind;
cb.recv_msgack_ind = rwnx_msgackind;
cb.recv_dbg_ind = rwnx_dbgind;
cb.send_data_cfm = rwnx_txdatacfm;
cb.recv_unsup_rx_vec_ind = rwnx_unsup_rx_vec_ind;
/* set the IPC environment */
rwnx_hw->ipc_env = (struct ipc_host_env_tag *)
kzalloc(sizeof(struct ipc_host_env_tag), GFP_KERNEL);
if (!rwnx_hw->ipc_env)
return -ENOMEM;
/* call the initialization of the IPC */
ipc_host_init(rwnx_hw->ipc_env, &cb,
(struct ipc_shared_env_tag *)shared_ram, rwnx_hw);
rwnx_cmd_mgr_init(rwnx_hw->cmd_mgr);
res = rwnx_elems_allocs(rwnx_hw);
if (res) {
kfree(rwnx_hw->ipc_env);
rwnx_hw->ipc_env = NULL;
}
return res;
}
/**
* rwnx_ipc_deinit() - Release IPC interface
*
* @rwnx_hw: Main driver data
*/
void rwnx_ipc_deinit(struct rwnx_hw *rwnx_hw)
{
RWNX_DBG(RWNX_FN_ENTRY_STR);
rwnx_ipc_tx_drain(rwnx_hw);
rwnx_cmd_mgr_deinit(rwnx_hw->cmd_mgr);
rwnx_elems_deallocs(rwnx_hw);
if (rwnx_hw->ipc_env) {
kfree(rwnx_hw->ipc_env);
rwnx_hw->ipc_env = NULL;
}
}
/**
* rwnx_ipc_start() - Start IPC interface
*
* @rwnx_hw: Main driver data
*/
void rwnx_ipc_start(struct rwnx_hw *rwnx_hw)
{
ipc_host_enable_irq(rwnx_hw->ipc_env, IPC_IRQ_E2A_ALL);
}
/**
* rwnx_ipc_stop() - Stop IPC interface
*
* @rwnx_hw: Main driver data
*/
void rwnx_ipc_stop(struct rwnx_hw *rwnx_hw)
{
ipc_host_disable_irq(rwnx_hw->ipc_env, IPC_IRQ_E2A_ALL);
}
/**
* rwnx_ipc_tx_drain() - Flush IPC TX buffers
*
* @rwnx_hw: Main driver data
*
* This assumes LMAC is still (tx wise) and there's no TX race until LMAC is up
* tx wise.
* This also lets both IPC sides remain in sync before resetting the LMAC,
* e.g with rwnx_send_reset.
*/
void rwnx_ipc_tx_drain(struct rwnx_hw *rwnx_hw)
{
struct sk_buff *skb;
RWNX_DBG(RWNX_FN_ENTRY_STR);
if (!rwnx_hw->ipc_env) {
printk(KERN_CRIT "%s: bypassing (restart must have failed)\n", __func__);
return;
}
while ((skb = ipc_host_tx_flush(rwnx_hw->ipc_env))) {
struct rwnx_sw_txhdr *sw_txhdr = ((struct rwnx_txhdr *)skb->data)->sw_hdr;
#ifdef CONFIG_RWNX_AMSDUS_TX
if (sw_txhdr->desc.api.host.packet_cnt > 1) {
struct rwnx_amsdu_txhdr *amsdu_txhdr;
list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) {
rwnx_ipc_buf_a2e_release(rwnx_hw, &amsdu_txhdr->ipc_data);
dev_kfree_skb_any(amsdu_txhdr->skb);
}
}
#endif
rwnx_ipc_buf_a2e_release(rwnx_hw, &sw_txhdr->ipc_data);
kmem_cache_free(rwnx_hw->sw_txhdr_cache, sw_txhdr);
skb_pull(skb, RWNX_TX_HEADROOM);
dev_kfree_skb_any(skb);
}
}
/**
* rwnx_ipc_tx_pending() - Check if TX frames are pending at FW level
*
* @rwnx_hw: Main driver data
*/
bool rwnx_ipc_tx_pending(struct rwnx_hw *rwnx_hw)
{
return ipc_host_tx_frames_pending(rwnx_hw->ipc_env);
}
/**
* rwnx_error_ind() - %DBG_ERROR_IND message callback
*
* @rwnx_hw: Main driver data
*
* This function triggers the UMH script call that will indicate to the user
* space the error that occurred and stored the debug dump. Once the UMH script
* is executed, the rwnx_umh_done() function has to be called.
*/
void rwnx_error_ind(struct rwnx_hw *rwnx_hw)
{
struct rwnx_ipc_buf *buf = &rwnx_hw->dbgdump.buf;
struct dbg_debug_dump_tag *dump = buf->addr;
rwnx_ipc_buf_e2a_sync(rwnx_hw, buf, 0);
dev_err(rwnx_hw->dev, "(type %d): dump received\n",
dump->dbg_info.error_type);
#ifdef CONFIG_RWNX_DEBUGFS
rwnx_hw->debugfs.trace_prst = true;
#endif
//rwnx_trigger_um_helper(&rwnx_hw->debugfs);
}
/**
* rwnx_umh_done() - Indicate User Mode helper finished
*
* @rwnx_hw: Main driver data
*
*/
extern void aicwf_pcie_host_init(struct ipc_host_env_tag *env, void *cb, struct ipc_shared_env_tag *shared_env_ptr, void *pthis);
void rwnx_umh_done(struct rwnx_hw *rwnx_hw)
{
if (!test_bit(RWNX_DEV_STARTED, &rwnx_hw->flags))
return;
/* this assumes error_ind won't trigger before ipc_host_dbginfo_push
is called and so does not irq protect (TODO) against error_ind */
#ifdef CONFIG_RWNX_DEBUGFS
rwnx_hw->debugfs.trace_prst = false;
#endif
ipc_host_dbginfo_push(rwnx_hw->ipc_env, &rwnx_hw->dbgdump.buf);
}
int rwnx_init_aic(struct rwnx_hw *rwnx_hw)
{
int res = 0;
struct ipc_shared_env_tag *shared_env = NULL;
RWNX_DBG(RWNX_FN_ENTRY_STR);
#ifdef AICWF_SDIO_SUPPORT
aicwf_sdio_host_init(&(rwnx_hw->sdio_env), NULL, NULL, rwnx_hw);
#endif
#ifdef AICWF_USB_SUPPORT
aicwf_usb_host_init(&(rwnx_hw->usb_env), NULL, NULL, rwnx_hw);
#endif
#ifdef AICWF_PCIE_SUPPORT
rwnx_hw->ipc_env = (struct ipc_host_env_tag *) kzalloc(sizeof(struct ipc_host_env_tag), GFP_KERNEL);
if (!rwnx_hw->ipc_env){
return -ENOMEM;
}
if(rwnx_hw->pcidev->chip_id == PRODUCT_ID_AIC8800D80) {
if (rwnx_hw->pcidev->bar_count == 1) {
aicwf_pcie_host_init(rwnx_hw->ipc_env, NULL, (struct ipc_shared_env_tag *)(rwnx_hw->pcidev->emb_shrm), rwnx_hw);
shared_env = (struct ipc_shared_env_tag *)(rwnx_hw->pcidev->emb_shrm);
} else {
aicwf_pcie_host_init(rwnx_hw->ipc_env, NULL, (struct ipc_shared_env_tag *)(rwnx_hw->pcidev->pci_bar0_vaddr + 0x1DC000), rwnx_hw);
shared_env = (struct ipc_shared_env_tag *)(rwnx_hw->pcidev->pci_bar0_vaddr + 0x1DC000);
}
} else {
aicwf_pcie_host_init(rwnx_hw->ipc_env, NULL, (struct ipc_shared_env_tag *)(rwnx_hw->pcidev->emb_shrm), rwnx_hw);
shared_env = (struct ipc_shared_env_tag *)(rwnx_hw->pcidev->emb_shrm);
}
res = rwnx_elems_allocs(rwnx_hw);
if (res) {
kfree(rwnx_hw->ipc_env);
rwnx_hw->ipc_env = NULL;
}
printk("sizeof struct ipc_shared_env_tag is %ld byte, offset=%ld, %ld, %ld , txdesc %lx\n", sizeof(struct ipc_shared_env_tag),
(u8 *)&shared_env->host_rxdesc - (u8 *)shared_env,
(u8 *)&shared_env->host_rxbuf - (u8 *)shared_env,
(u8 *)&shared_env->buffered - (u8 *)shared_env,
(u8 *)&rwnx_hw->ipc_env->shared->txdesc - (u8 *)shared_env);
printk("txdesc size %ld\n", sizeof(rwnx_hw->ipc_env->shared->txdesc));
#endif
rwnx_cmd_mgr_init(rwnx_hw->cmd_mgr);
return res;
}
void rwnx_aic_deinit(struct rwnx_hw *rwnx_hw)
{
RWNX_DBG(RWNX_FN_ENTRY_STR);
//rwnx_ipc_tx_drain(rwnx_hw);
rwnx_elems_deallocs(rwnx_hw);
if (rwnx_hw->ipc_env) {
kfree(rwnx_hw->ipc_env);
rwnx_hw->ipc_env = NULL;
}
}