diff options
Diffstat (limited to 'drivers/net/wireless/eswin/ecrnx_utils.c')
-rw-r--r-- | drivers/net/wireless/eswin/ecrnx_utils.c | 1333 |
1 files changed, 1333 insertions, 0 deletions
diff --git a/drivers/net/wireless/eswin/ecrnx_utils.c b/drivers/net/wireless/eswin/ecrnx_utils.c new file mode 100644 index 000000000000..e60785c66f4d --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_utils.c @@ -0,0 +1,1333 @@ +/** + * ecrnx_utils.c + * + * IPC utility function definitions + * + * Copyright (C) ESWIN 2015-2020 + */ +#include "ecrnx_utils.h" +#include "ecrnx_defs.h" +#include "ecrnx_rx.h" +#include "ecrnx_tx.h" +#include "ecrnx_msg_rx.h" +#include "ecrnx_debugfs.h" +#include "ecrnx_prof.h" +#include "ipc_host.h" + +#ifdef CONFIG_ECRNX_ESWIN_SDIO +#include "eswin_utils.h" +#include "ecrnx_sdio.h" +#include "sdio.h" +#elif defined(CONFIG_ECRNX_ESWIN_USB) +#include "eswin_utils.h" +#include "ecrnx_usb.h" +#include "usb.h" +#endif + + +/** + * ecrnx_ipc_elem_pool_allocs() - Allocate and push to fw a pool of buffer. + * + * @ecrnx_hw: Main driver structure + * @pool: Pool to allocate + * @nb: Size of the pool to allocate + * @elem_size: SIze of one pool element + * @pool_name: Name of the pool + * @push: Function to push one pool element to fw + * + * This function will allocate an array to store the list of element addresses, + * a dma pool and @nb element in the dma pool. + * Each element is set with '0' and then push to fw using the @push function. + * It assumes that pointer inside @ipc parameter are set to NULL at start. + * + * Return: 0 on success and <0 upon error. If error is returned any allocated + * memory is NOT freed and ecrnx_ipc_elem_pool_deallocs() must be called. + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_elem_pool_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_pool *pool, + int nb, size_t elem_size, char *pool_name, + int (*push)(struct ipc_host_env_tag *, + void *, uint32_t)) +{ + struct ecrnx_ipc_elem *buf; + int i; + + pool->nb = 0; + + /* allocate buf array */ + pool->buf = kmalloc(nb * sizeof(struct ecrnx_ipc_elem), GFP_KERNEL); + if (!pool->buf) { + dev_err(ecrnx_hw->dev, "Allocation of buffer array for %s failed\n", + pool_name); + return -ENOMEM; + } + + /* allocate dma pool */ + pool->pool = dma_pool_create(pool_name, ecrnx_hw->dev, elem_size, + cache_line_size(), 0); + if (!pool->pool) { + dev_err(ecrnx_hw->dev, "Allocation of dma pool %s failed\n", + pool_name); + return -ENOMEM; + } + + for (i = 0, buf = pool->buf; i < nb; buf++, i++) { + + /* allocate an elem */ + buf->addr = dma_pool_alloc(pool->pool, GFP_KERNEL, &buf->dma_addr); + if (!buf->addr) { + dev_err(ecrnx_hw->dev, "Allocation of block %d/%d in %s failed\n", + (i + 1), nb, pool_name); + return -ENOMEM; + } + pool->nb++; + + /* reset the element */ + memset(buf->addr, 0, elem_size); + + /* push it to FW */ + push(ecrnx_hw->ipc_env, buf, (uint32_t)buf->dma_addr); + } + + return 0; +} +#endif +/** + * ecrnx_ipc_elem_pool_deallocs() - Free all memory allocated for a pool + * + * @pool: Pool to free + * + * Must be call once after ecrnx_ipc_elem_pool_allocs(), even if it returned + * an error + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_elem_pool_deallocs(struct ecrnx_ipc_elem_pool *pool) +{ + struct ecrnx_ipc_elem *buf; + int i; + + for (i = 0, buf = pool->buf; 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->buf) + kfree(pool->buf); + pool->buf = NULL; +} +#endif +/** + * ecrnx_ipc_elem_var_allocs - Alloc a single ipc buffer and push it to fw + * + * @ecrnx_hw: Main driver structure + * @elem: Element to allocate + * @elem_size: Size of the element to allcoate + * @dir: DMA direction + * @buf: If not NULL, used this buffer instead of allocating a new one. It must + * be @elem_size long and be allocated by kmalloc as kfree will be called. + * @init: Pointer to initial data to write in buffer before DMA sync. Needed + * only if direction is DMA_TO_DEVICE. If set it is assume that its size is + * @elem_size. + * @push: Function to push the element to fw. May be set to NULL. + * + * It allocates a buffer (or use the one provided with @buf), initializes it if + * @init is set, map buffer for DMA transfer, initializes @elem and push buffer + * to FW if @push is seet. + * + * Return: 0 on success and <0 upon error. If error is returned any allocated + * memory has been freed (including @buf if set). + */ +int ecrnx_ipc_elem_var_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_var *elem, size_t elem_size, + enum dma_data_direction dir, + void *buf, const void *init, + void (*push)(struct ipc_host_env_tag *, uint32_t)) +{ + if (buf) { + elem->addr = buf; + } else { + elem->addr = kmalloc(elem_size, GFP_KERNEL); + if (!elem->addr) { + dev_err(ecrnx_hw->dev, "Allocation of ipc buffer failed\n"); + return -ENOMEM; + } + } + elem->size = elem_size; + + if ((dir == DMA_TO_DEVICE) && init) { + memcpy(elem->addr, init, elem_size); + } + +#ifdef CONFIG_ECRNX_ESWIN + elem->dma_addr = (ptr_addr)elem->addr; +#else + elem->dma_addr = dma_map_single(ecrnx_hw->dev, elem->addr, elem_size, dir); + if (dma_mapping_error(ecrnx_hw->dev, elem->dma_addr)) { + dev_err(ecrnx_hw->dev, "DMA mapping failed\n"); + kfree(elem->addr); + elem->addr = NULL; + return -EIO; + } + + if (push) + push(ecrnx_hw->ipc_env, elem->dma_addr); +#endif + return 0; +} + +/** + * ecrnx_ipc_elem_var_deallocs() - Free memory allocated for a single ipc buffer + * + * @ecrnx_hw: Main driver structure + * @elem: Element to free + */ +void ecrnx_ipc_elem_var_deallocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_var *elem) +{ + if (!elem->addr) + return; +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, elem->dma_addr, elem->size, DMA_TO_DEVICE); +#endif + kfree(elem->addr); + elem->addr = NULL; +} + +/** + * ecrnx_ipc_skb_elem_allocs() - Allocate and push a skb buffer for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that will contain the address of the buffer + */ +int ecrnx_ipc_skb_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem, size_t skb_size, + enum dma_data_direction dir, + int (*push)(struct ipc_host_env_tag *, + void *, uint32_t)) +{ + elem->skb = dev_alloc_skb(skb_size); + if (unlikely(!elem->skb)) { + dev_err(ecrnx_hw->dev, "Allocation of ipc skb failed\n"); + return -ENOMEM; + } + + elem->dma_addr = dma_map_single(ecrnx_hw->dev, elem->skb->data, skb_size, dir); + if (unlikely(dma_mapping_error(ecrnx_hw->dev, elem->dma_addr))) { + dev_err(ecrnx_hw->dev, "DMA mapping failed\n"); + dev_kfree_skb(elem->skb); + elem->skb = NULL; + return -EIO; + } + + if (push){ + push(ecrnx_hw->ipc_env, elem, elem->dma_addr); + } + return 0; +} + +/** + * ecrnx_ipc_skb_elem_deallocs() - Free a skb buffer allocated for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that contains the address of the buffer + * @skb_size: size of the skb buffer data + * @dir: DMA direction + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_skb_elem_deallocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem, + size_t skb_size, enum dma_data_direction dir) +{ + if (elem->skb) { + dma_unmap_single(ecrnx_hw->dev, elem->dma_addr, skb_size, dir); + dev_kfree_skb(elem->skb); + elem->skb = NULL; + } +} +#endif +/** + * ecrnx_ipc_unsup_rx_vec_elem_allocs() - Allocate and push an unsupported + * RX vector buffer for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that will contain the address of the buffer + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_unsup_rx_vec_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem) +{ + struct rx_vector_desc *rxdesc; + + if (ecrnx_ipc_skb_elem_allocs(ecrnx_hw, elem, + ecrnx_hw->ipc_env->unsuprxvec_bufsz, DMA_FROM_DEVICE, NULL)) + return -ENOMEM; + + rxdesc = (struct rx_vector_desc *) elem->skb->data; + rxdesc->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, + elem->dma_addr + offsetof(struct rx_vector_desc, pattern), + sizeof(rxdesc->pattern), DMA_BIDIRECTIONAL); + + ipc_host_unsup_rx_vec_buf_push(ecrnx_hw->ipc_env, elem, (u32) elem->dma_addr); + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_deallocs() - Free all unsupported rx vector buffer + * allocated for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_unsup_rx_vec_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->unsuprxvec_bufnb; + + if (!ecrnx_hw->e2aunsuprxvec_elems) + return; + + for (i = 0, elem = ecrnx_hw->e2aunsuprxvec_elems; i < nb; i++, elem++) { + ecrnx_ipc_skb_elem_deallocs(ecrnx_hw, elem, ecrnx_hw->ipc_env->unsuprxvec_bufsz, DMA_FROM_DEVICE); + } + + kfree(ecrnx_hw->e2aunsuprxvec_elems); + ecrnx_hw->e2aunsuprxvec_elems = NULL; +} +#endif + +/** +* ecrnx_ipc_unsup_rx_vec_elems_allocs() - Allocate and push all unsupported RX +* vector buffer for the FW +* +* @ecrnx_hw: Main driver data +*/ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_unsup_rx_vec_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->unsuprxvec_bufnb; + + ecrnx_hw->e2aunsuprxvec_elems = kzalloc(nb * sizeof(struct ecrnx_ipc_skb_elem), + GFP_KERNEL); + if (!ecrnx_hw->e2aunsuprxvec_elems) { + dev_err(ecrnx_hw->dev, "Failed to allocate unsuprxvec_elems\n"); + return -ENOMEM; + } + + for (i = 0, elem = ecrnx_hw->e2aunsuprxvec_elems; i < nb; i++, elem++) + { + if (ecrnx_ipc_unsup_rx_vec_elem_allocs(ecrnx_hw, elem)) { + dev_err(ecrnx_hw->dev, "Failed to allocate unsuprxvec buf %d/%d\n", + i + 1, nb); + return -ENOMEM; + } + } + return 0; +} +#endif + +#ifdef CONFIG_ECRNX_SOFTMAC +/** + * ecrnx_ipc_rxbuf_elem_allocs() - Allocate and push a rx buffer for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that will contain the address of the buffer + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_rxbuf_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem) +{ + struct hw_rxhdr *hw_rxhdr; + + if (ecrnx_ipc_skb_elem_allocs(ecrnx_hw, elem, + ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE, NULL)) + return -ENOMEM; + + hw_rxhdr = (struct hw_rxhdr *) elem->skb->data; + hw_rxhdr->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, + elem->dma_addr + offsetof(struct hw_rxhdr, pattern), + sizeof(hw_rxhdr->pattern), DMA_BIDIRECTIONAL); + + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, elem, (u32) elem->dma_addr); + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_repush() - Reset and repush an already allocated RX buffer + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that contains the address of the buffer + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem) +{ + struct sk_buff *skb = elem->skb; + int pattern_offset = sizeof(struct hw_rxhdr); + + ((struct hw_rxhdr *)skb->data)->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, + pattern_offset, DMA_BIDIRECTIONAL); + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_allocs() - Allocate and push all RX buffer for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_rxbuf_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->rx_bufnb; + + ecrnx_hw->rxbuf_elems = kzalloc(nb * sizeof(struct ecrnx_ipc_skb_elem), + GFP_KERNEL); + if (!ecrnx_hw->rxbuf_elems) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx_elems\n"); + return -ENOMEM; + } + + for (i = 0, elem = ecrnx_hw->rxbuf_elems; i < nb; i++, elem++) { + if (ecrnx_ipc_rxbuf_elem_allocs(ecrnx_hw, elem)) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx buf %d/%d\n", + i + 1, nb); + return -ENOMEM; + } + } + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_deallocs() - Free all RX buffer allocated for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_rxbuf_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->rx_bufnb; + + if (!ecrnx_hw->rxbuf_elems) + return; + + for (i = 0, elem = ecrnx_hw->rxbuf_elems; i < nb; i++, elem++) { + ecrnx_ipc_skb_elem_deallocs(ecrnx_hw, elem, ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE); + } + + kfree(ecrnx_hw->rxbuf_elems); + ecrnx_hw->rxbuf_elems = NULL; +} +#endif + +#else /* ! CONFIG_ECRNX_SOFTMAC */ + +/** + * ecrnx_ipc_rxdesc_elem_repush() - Repush a rxdesc to FW + * + * @ecrnx_hw: Main driver data + * @elem: Rx desc to repush + * + * Once rx buffer has been received, the rxdesc used by FW to upload this + * buffer can be re-used for another rx buffer. + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxdesc_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem *elem) +{ + struct rxdesc_tag *rxdesc = elem->addr; + rxdesc->status = 0; + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, + sizeof(struct rxdesc_tag), DMA_BIDIRECTIONAL); + ipc_host_rxdesc_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); +} +#endif +/** + * ecrnx_ipc_rxbuf_elem_allocs() - Allocate and push a RX buffer for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_rxbuf_elem_allocs(struct ecrnx_hw *ecrnx_hw) +{ + struct sk_buff *skb; + struct hw_rxhdr *hw_rxhdr; + dma_addr_t dma_addr; + int size = ecrnx_hw->ipc_env->rx_bufsz; + int nb, idx; + + skb = dev_alloc_skb(size); + if (unlikely(!skb)) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx buffer\n"); + return -ENOMEM; + } + + dma_addr = dma_map_single(ecrnx_hw->dev, skb->data, size, DMA_FROM_DEVICE); + + if (unlikely(dma_mapping_error(ecrnx_hw->dev, dma_addr))) { + dev_err(ecrnx_hw->dev, "Failed to map rx buffer\n"); + goto err_skb; + } + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + hw_rxhdr->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, + dma_addr + offsetof(struct hw_rxhdr, pattern), + sizeof(hw_rxhdr->pattern), DMA_BIDIRECTIONAL); + + /* Find first free slot */ + nb = 0; + idx = ecrnx_hw->rxbuf_elems.idx; + while (ecrnx_hw->rxbuf_elems.skb[idx] && nb < ECRNX_RXBUFF_MAX) { + idx = ( idx + 1 ) % ECRNX_RXBUFF_MAX; + nb++; + } + + if (WARN((nb == ECRNX_RXBUFF_MAX), "No more free space for rxbuff")) { + goto err_dma; + } + + ecrnx_hw->rxbuf_elems.skb[idx] = skb; + + /* Save info in skb control buffer */ + ECRNX_RXBUFF_DMA_ADDR_SET(skb, dma_addr); + ECRNX_RXBUFF_PATTERN_SET(skb, ecrnx_rxbuff_pattern); + ECRNX_RXBUFF_IDX_SET(skb, idx); + + /* Push buffer to FW */ + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, ECRNX_RXBUFF_IDX_TO_HOSTID(idx), + dma_addr); + + /* Save idx so that on next push the free slot will be found quicker */ + ecrnx_hw->rxbuf_elems.idx = ( idx + 1 ) % ECRNX_RXBUFF_MAX; + + return 0; + + err_dma: + dma_unmap_single(ecrnx_hw->dev, dma_addr, size, DMA_FROM_DEVICE); + err_skb: + dev_kfree_skb(skb); + return -ENOMEM; + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_repush() - Repush a rxbuf to FW + * + * @ecrnx_hw: Main driver data + * @skb: Skb to repush + * + * In case a skb is not forwarded to upper layer it can be re-used. + * It is assumed that @skb has been verified before calling this function and + * that it is a valid rx buffer + * (i.e. skb == ecrnx_hw->rxbuf_elems.skb[ECRNX_RXBUFF_IDX_GET(skb)]) + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct sk_buff *skb) +{ + dma_addr_t dma_addr; + struct hw_rxhdr *hw_rxhdr = (struct hw_rxhdr *)skb->data; + int idx; + + /* reset pattern */ + hw_rxhdr->pattern = 0; + dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + dma_sync_single_for_device(ecrnx_hw->dev, + dma_addr + offsetof(struct hw_rxhdr, pattern), + sizeof(hw_rxhdr->pattern), DMA_BIDIRECTIONAL); + + /* re-push buffer to FW */ + idx = ECRNX_RXBUFF_IDX_GET(skb); + + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, ECRNX_RXBUFF_IDX_TO_HOSTID(idx), + dma_addr); +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_allocs() - Allocate and push all RX buffer for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_rxbuf_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + //int i, nb = ecrnx_hw->ipc_env->rx_bufnb; + int i, nb = 0; + + for (i = 0; i < ECRNX_RXBUFF_MAX; i++) { + ecrnx_hw->rxbuf_elems.skb[i] = NULL; + } + ecrnx_hw->rxbuf_elems.idx = 0; + + for (i = 0; i < nb; i++) { + if (ecrnx_ipc_rxbuf_elem_allocs(ecrnx_hw)) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx buf %d/%d\n", + i + 1, nb); + return -ENOMEM; + } + } + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_deallocs() - Free all RX buffer allocated for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_rxbuf_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + struct sk_buff *skb; + int i; + + for (i = 0; i < ECRNX_RXBUFF_MAX; i++) { + if (ecrnx_hw->rxbuf_elems.skb[i]) { + skb = ecrnx_hw->rxbuf_elems.skb[i]; + dma_unmap_single(ecrnx_hw->dev, ECRNX_RXBUFF_DMA_ADDR_GET(skb), + ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE); + dev_kfree_skb(skb); + ecrnx_hw->rxbuf_elems.skb[i] = NULL; + } + } +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_pull() - Extract a skb from local table + * + * @ecrnx_hw: Main driver data + * @skb: SKb to extract for table + * + * After checking that skb is actually a pointer of local table, extract it + * from the table. + * When buffer is removed, DMA mapping is remove which has the effect to + * synchronize the buffer for the cpu. + * To be called before passing skb to upper layer. + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_pull(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + unsigned int idx = ECRNX_RXBUFF_IDX_GET(skb); + + if (ECRNX_RXBUFF_VALID_IDX(idx) && (ecrnx_hw->rxbuf_elems.skb[idx] == skb)) { + dma_addr_t dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + ecrnx_hw->rxbuf_elems.skb[idx] = NULL; + dma_unmap_single(ecrnx_hw->dev, dma_addr, + ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE); + } else { + WARN(1, "Incorrect rxbuff idx skb=%p table[%u]=%p", skb, idx, + idx < ECRNX_RXBUFF_MAX ? ecrnx_hw->rxbuf_elems.skb[idx] : NULL); + } + + /* Reset the pattern and idx */ + ECRNX_RXBUFF_PATTERN_SET(skb, 0); + ECRNX_RXBUFF_IDX_SET(skb, ECRNX_RXBUFF_MAX); +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_sync() - Sync part of a RX buffer + * + * @ecrnx_hw: Main driver data + * @skb: SKb to sync + * @len: Len to sync + * + * After checking that skb is actually a pointer of local table, sync @p len + * bytes of the buffer for CPU. Buffer is not removed from the table + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_sync(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + int len) +{ + unsigned int idx = ECRNX_RXBUFF_IDX_GET(skb); + + if (ECRNX_RXBUFF_VALID_IDX(idx) && (ecrnx_hw->rxbuf_elems.skb[idx] == skb)) { + dma_addr_t dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + dma_sync_single_for_cpu(ecrnx_hw->dev, dma_addr, len, DMA_FROM_DEVICE); + } else { + WARN(1, "Incorrect rxbuff idx skb=%p table[%u]=%p", skb, idx, + idx < ECRNX_RXBUFF_MAX ? ecrnx_hw->rxbuf_elems.skb[idx] : NULL); + } +} +#endif +#endif /* ! CONFIG_ECRNX_SOFTMAC */ + +/** + * ecrnx_elems_deallocs() - Deallocate IPC storage elements. + * @ecrnx_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. + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + ecrnx_ipc_rxbuf_elems_deallocs(ecrnx_hw); + ecrnx_ipc_unsup_rx_vec_elems_deallocs(ecrnx_hw); +#ifdef CONFIG_ECRNX_FULLMAC + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->e2arxdesc_pool); +#endif + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->e2amsgs_pool); + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->dbgmsgs_pool); + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->e2aradars_pool); + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &ecrnx_hw->pattern_elem); + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &ecrnx_hw->dbgdump_elem.buf); +} +#endif + +/** + * ecrnx_elems_allocs() - Allocate IPC storage elements. + * @ecrnx_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 ecrnx_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#ifndef CONFIG_ECRNX_ESWIN + if (dma_set_coherent_mask(ecrnx_hw->dev, DMA_BIT_MASK(32))) + goto err_alloc; + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->e2amsgs_pool, + ecrnx_hw->ipc_env->ipc_e2amsg_bufnb, + ecrnx_hw->ipc_env->ipc_e2amsg_bufsz, + "ecrnx_ipc_e2amsgs_pool", + ipc_host_msgbuf_push)) + goto err_alloc; + + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->dbgmsgs_pool, + ecrnx_hw->ipc_env->ipc_dbg_bufnb, + ecrnx_hw->ipc_env->ipc_dbg_bufsz, + "ecrnx_ipc_dbgmsgs_pool", + ipc_host_dbgbuf_push)) + goto err_alloc; + + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->e2aradars_pool, + ecrnx_hw->ipc_env->radar_bufnb, + ecrnx_hw->ipc_env->radar_bufsz, + "ecrnx_ipc_e2aradars_pool", + ipc_host_radarbuf_push)) + goto err_alloc; + + if (ecrnx_ipc_unsup_rx_vec_elems_allocs(ecrnx_hw)) + #error 1111 + goto err_alloc; + + if (ecrnx_ipc_elem_var_allocs(ecrnx_hw, &ecrnx_hw->pattern_elem, + sizeof(u32), DMA_TO_DEVICE, + NULL, &ecrnx_rxbuff_pattern, + ipc_host_patt_addr_push)) + goto err_alloc; + + if (ecrnx_ipc_elem_var_allocs(ecrnx_hw, &ecrnx_hw->dbgdump_elem.buf, + sizeof(struct dbg_debug_dump_tag), + DMA_FROM_DEVICE, NULL, NULL, + ipc_host_dbginfobuf_push)) + goto err_alloc; + + /* + * 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 ecrnx_handle_dynparams() execution) + */ + +#ifdef CONFIG_ECRNX_FULLMAC + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->e2arxdesc_pool, + ecrnx_hw->ipc_env->rxdesc_nb, + sizeof(struct rxdesc_tag), + "ecrnx_ipc_e2arxdesc_pool", + ipc_host_rxdesc_push)) + goto err_alloc; + +#endif /* CONFIG_ECRNX_FULLMAC */ + + return 0; + +err_alloc: + ecrnx_elems_deallocs(ecrnx_hw); + return -ENOMEM; +#else + return 0; +#endif +} + +/** + * ecrnx_ipc_msg_push() - Push a msg to IPC queue + * + * @ecrnx_hw: Main driver data + * @msg_buf: Pointer to message + * @len: Size, in bytes, of message + */ +void ecrnx_ipc_msg_push(struct ecrnx_hw *ecrnx_hw, void *msg_buf, uint16_t len) +{ + ecrnx_hw->msg_tx++; + ipc_host_msg_push(ecrnx_hw->ipc_env, msg_buf, len); +} + +/** + * ecrnx_ipc_txdesc_push() - Push a txdesc to FW + * + * @ecrnx_hw: Main driver data + * @tx_desc: Pointer on &struct txdesc_api to push to FW + * @hostid: Pointer save in ipc env to retrieve tx buffer upon confirmation. + * @hw_queue: Hw queue to push txdesc to + * @user: User position to push the txdesc to. It must be set to 0 if MU-MIMMO + * is not used. + */ +void ecrnx_ipc_txdesc_push(struct ecrnx_hw *ecrnx_hw, void *tx_desc, + void *hostid, int hw_queue, int user) +{ + +#if !defined(CONFIG_ECRNX_ESWIN_SDIO) && !defined(CONFIG_ECRNX_ESWIN_USB) + volatile struct txdesc_host *txdesc_host; + u32 *src, *dst; + int i; + + txdesc_host = ipc_host_txdesc_get(ecrnx_hw->ipc_env, hw_queue, user); + BUG_ON(!txdesc_host); + + dst = (typeof(dst))&txdesc_host->api; + src = (typeof(src))tx_desc; + for (i = 0; i < sizeof(txdesc_host->api) / sizeof(*src); i++) + *dst++ = *src++; + + wmb(); /* vs desc */ + + ipc_host_txdesc_push(ecrnx_hw->ipc_env, hw_queue, user, hostid); +#else + ecrnx_frame_send(ecrnx_hw, tx_desc, hostid, hw_queue, user); +#endif +} + +/** + * ecrnx_ipc_fw_trace_desc_get() - Return pointer to the start of trace + * description in IPC environment + * + * @ecrnx_hw: Main driver data + */ +void *ecrnx_ipc_fw_trace_desc_get(struct ecrnx_hw *ecrnx_hw) +{ +#ifndef CONFIG_ECRNX_ESWIN + return (void *)&(ecrnx_hw->ipc_env->shared->trace_pattern); +#else + return NULL; +#endif +} + +#ifndef CONFIG_ECRNX_ESWIN +/** + * ecrnx_ipc_sta_buffer_init - Initialize counter of bufferred data for a given sta + * + * @ecrnx_hw: Main driver data + * @sta_idx: Index of the station to initialize + */ +void ecrnx_ipc_sta_buffer_init(struct ecrnx_hw *ecrnx_hw, int sta_idx) +{ + int i; + volatile u32_l *buffered; + + if (sta_idx >= NX_REMOTE_STA_MAX) + return; + + buffered = ecrnx_hw->ipc_env->shared->buffered[sta_idx]; + + for (i = 0; i < TID_MAX; i++) { + *buffered++ = 0; + } +} +#endif +/** + * ecrnx_ipc_sta_buffer - Update counter of bufferred data for a given sta + * + * @ecrnx_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 ecrnx_ipc_sta_buffer(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, int tid, int size) +{ +#ifndef CONFIG_ECRNX_ESWIN + u32_l *buffered; + + if (!sta) + return; + + if ((sta->sta_idx >= NX_REMOTE_STA_MAX) || (tid >= TID_MAX)) + return; + + buffered = &ecrnx_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; + } +#endif +} + +/** + * ecrnx_msgind() - IRQ handler callback for %IPC_IRQ_E2A_MSG + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from e2amsgs_pool + */ +static u8 ecrnx_msgind(void *pthis, void *hostid) +{ + struct ecrnx_hw *ecrnx_hw = pthis; + u8 ret = 0; +#ifndef CONFIG_ECRNX_ESWIN + struct ecrnx_ipc_elem *elem = hostid; + struct ipc_e2a_msg *msg = elem->addr; + + REG_SW_SET_PROFILING(ecrnx_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; + } +#else + struct ipc_e2a_msg *msg = NULL; + + ecrnx_hw->msg_rx++; + ECRNX_DBG("%s enter 0x%x, 0x%x!!\n", __func__, pthis, hostid); + if(!pthis || !hostid){ + ECRNX_ERR(" %s input param error!! \n", __func__); + return ret; + } + msg = hostid; +#endif + /* Relay further actions to the msg parser */ + ecrnx_rx_handle_msg(ecrnx_hw, msg); + +#ifndef CONFIG_ECRNX_ESWIN + /* Reset the msg element and re-use it */ + msg->pattern = 0; + wmb(); + + /* Push back the buffer to the LMAC */ + ipc_host_msgbuf_push(ecrnx_hw->ipc_env, elem, elem->dma_addr); + +msg_no_push: + REG_SW_CLEAR_PROFILING(ecrnx_hw, SW_PROF_MSGIND); +#endif + ECRNX_DBG("%s exit!!", __func__); + return ret; +} + +/** + * ecrnx_msgackind() - IRQ handler callback for %IPC_IRQ_E2A_MSG_ACK + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to command acknoledged + */ +static u8 ecrnx_msgackind(void *pthis, void *hostid) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + + ecrnx_hw->msg_tx_done++; + ecrnx_hw->cmd_mgr.llind(&ecrnx_hw->cmd_mgr, (struct ecrnx_cmd *)hostid); + return -1; +} + +/** + * ecrnx_radarind() - IRQ handler callback for %IPC_IRQ_E2A_RADAR + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from e2aradars_pool + */ +static u8 ecrnx_radarind(void *pthis, void *hostid) +{ +#ifdef CONFIG_ECRNX_RADAR + struct ecrnx_hw *ecrnx_hw = pthis; + struct ecrnx_ipc_elem *elem = hostid; + struct radar_pulse_array_desc *pulses = elem->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 (ecrnx_radar_detection_is_enable(&ecrnx_hw->radar, pulses->idx)) { + /* Save the received pulses only if radar detection is enabled */ + for (i = 0; i < pulses->cnt; i++) { + struct ecrnx_radar_pulses *p = &ecrnx_hw->radar.pulses[pulses->idx]; + + p->buffer[p->index] = pulses->pulse[i]; + p->index = (p->index + 1) % ECRNX_RADAR_PULSE_MAX; + if (p->count < ECRNX_RADAR_PULSE_MAX) + p->count++; + } + + /* Defer pulse processing in separate work */ + if (! work_pending(&ecrnx_hw->radar.detection_work)) + schedule_work(&ecrnx_hw->radar.detection_work); + } + + /* Reset the radar element and re-use it */ + pulses->cnt = 0; +#ifndef CONFIG_ECRNX_ESWIN + wmb(); + + /* Push back the buffer to the LMAC */ + ipc_host_radarbuf_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); +#endif +radar_no_push: + return ret; +#else + return -1; +#endif +} + +/** + * ecrnx_prim_tbtt_ind() - IRQ handler callback for %IPC_IRQ_E2A_TBTT_PRIM + * + * @pthis: Pointer to main driver data + */ +static void ecrnx_prim_tbtt_ind(void *pthis) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + ecrnx_tx_bcns(ecrnx_hw); +#endif /* CONFIG_ECRNX_SOFTMAC */ +} + +/** + * ecrnx_sec_tbtt_ind() - IRQ handler callback for %IPC_IRQ_E2A_TBTT_SEC + * + * @pthis: Pointer to main driver data + */ +static void ecrnx_sec_tbtt_ind(void *pthis) +{ +} + +/** + * ecrnx_dbgind() - IRQ handler callback for %IPC_IRQ_E2A_DBG + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from dbgmsgs_pool + */ + +#ifdef CONFIG_ECRNX_ESWIN_USB + extern void usb_dbg_printf(void * data, int len); +#endif +static u8 ecrnx_dbgind(void *pthis, void *hostid) +{ + u8 ret = 0; +#ifndef CONFIG_ECRNX_ESWIN + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + struct ecrnx_ipc_elem *elem = hostid; + struct ipc_dbg_msg *dbg_msg = elem->addr; + + REG_SW_SET_PROFILING(ecrnx_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 element and re-use it */ + dbg_msg->pattern = 0; + wmb(); + + /* Push back the buffer to the LMAC */ + ipc_host_dbgbuf_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); + +dbg_no_push: + REG_SW_CLEAR_PROFILING(ecrnx_hw, SW_PROF_DBGIND); + +#else + struct sk_buff *skb = (struct sk_buff *)hostid; +#ifdef CONFIG_ECRNX_ESWIN_USB + usb_dbg_printf(skb->data, skb->len); +#else + uint8_t string[IPC_DBG_PARAM_SIZE] = {0}; + if(skb->len < IPC_DBG_PARAM_SIZE) + { + memcpy(string, skb->data, skb->len); + } + else + { + printk("waring: string buff no enough \n"); + memcpy(string, skb->data, IPC_DBG_PARAM_SIZE-1); + } + ECRNX_PRINT("%s %s", (char *)FW_STR, (char *)string); +#endif +#endif + + return ret; +} + +/** + * ecrnx_ipc_rxbuf_init() - Allocate and initialize RX buffers. + * + * @ecrnx_hw: Main driver data + * @rx_bufsz: Size of the buffer to be allocated + * + * This function updates the RX buffer size according to the parameter and allocates the + * RX buffers + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_rxbuf_init(struct ecrnx_hw *ecrnx_hw, uint32_t rx_bufsz) +{ + ecrnx_hw->ipc_env->rx_bufsz = rx_bufsz; + return(ecrnx_ipc_rxbuf_elems_allocs(ecrnx_hw)); +} +#endif +/** + * ecrnx_ipc_init() - Initialize IPC interface. + * + * @ecrnx_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 ecrnx_ipc_init(struct ecrnx_hw *ecrnx_hw, u8 *shared_ram) +{ + struct ipc_host_cb_tag cb; + int res; + ECRNX_DBG("%s entry!!", __func__); + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* initialize the API interface */ + cb.recv_data_ind = ecrnx_rxdataind; + cb.recv_radar_ind = ecrnx_radarind; + cb.recv_msg_ind = ecrnx_msgind; + cb.recv_msgack_ind = ecrnx_msgackind; + cb.recv_dbg_ind = ecrnx_dbgind; + cb.send_data_cfm = ecrnx_txdatacfm; + cb.handle_data_cfm = ecrnx_handle_tx_datacfm; + + cb.prim_tbtt_ind = ecrnx_prim_tbtt_ind; + cb.sec_tbtt_ind = ecrnx_sec_tbtt_ind; + cb.recv_unsup_rx_vec_ind = ecrnx_unsup_rx_vec_ind; + + /* set the IPC environment */ + ecrnx_hw->ipc_env = (struct ipc_host_env_tag *) + kzalloc(sizeof(struct ipc_host_env_tag), GFP_KERNEL); + + if (!ecrnx_hw->ipc_env) + return -ENOMEM; + + /* call the initialization of the IPC */ + ipc_host_init(ecrnx_hw->ipc_env, &cb, + (struct ipc_shared_env_tag *)shared_ram, ecrnx_hw); + + ecrnx_cmd_mgr_init(&ecrnx_hw->cmd_mgr); + + ecrnx_rx_reord_init(ecrnx_hw); +#ifdef CONFIG_ECRNX_ESWIN_SDIO + ecrnx_sdio_init(ecrnx_hw); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + ecrnx_usb_init(ecrnx_hw); +#endif + + res = ecrnx_elems_allocs(ecrnx_hw); + if (res) { + + kfree(ecrnx_hw->ipc_env); + ecrnx_hw->ipc_env = NULL; + } + ECRNX_DBG("%s exit!!", __func__); + return res; +} + +/** + * ecrnx_ipc_deinit() - Release IPC interface + * + * @ecrnx_hw: Main driver data + */ +void ecrnx_ipc_deinit(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_ipc_tx_drain(ecrnx_hw); + ecrnx_cmd_mgr_deinit(&ecrnx_hw->cmd_mgr); +#ifndef CONFIG_ECRNX_ESWIN + ecrnx_elems_deallocs(ecrnx_hw); +#endif + + ecrnx_rx_reord_deinit(ecrnx_hw); +#ifdef CONFIG_ECRNX_ESWIN_SDIO + if (ecrnx_hw->ipc_env->shared) { + kfree(ecrnx_hw->ipc_env->shared); + ecrnx_hw->ipc_env->shared = NULL; + } + ecrnx_sdio_deinit(ecrnx_hw); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + ecrnx_usb_deinit(ecrnx_hw); +#endif + + if (ecrnx_hw->ipc_env) { + kfree(ecrnx_hw->ipc_env); + ecrnx_hw->ipc_env = NULL; + } +} + +/** + * ecrnx_ipc_start() - Start IPC interface + * + * @ecrnx_hw: Main driver data + */ +void ecrnx_ipc_start(struct ecrnx_hw *ecrnx_hw) +{ + ipc_host_enable_irq(ecrnx_hw->ipc_env, IPC_IRQ_E2A_ALL); +} + +/** + * ecrnx_ipc_stop() - Stop IPC interface + * + * @ecrnx_hw: Main driver data + */ +void ecrnx_ipc_stop(struct ecrnx_hw *ecrnx_hw) +{ + ipc_host_disable_irq(ecrnx_hw->ipc_env, IPC_IRQ_E2A_ALL); +} + +/** + * ecrnx_ipc_tx_drain() - Flush IPC TX buffers + * + * @ecrnx_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 ecrnx_send_reset. + */ +void ecrnx_ipc_tx_drain(struct ecrnx_hw *ecrnx_hw) +{ + int i, j; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (!ecrnx_hw->ipc_env) { + printk(KERN_CRIT "%s: bypassing (restart must have failed)\n", __func__); + return; + } + + for (i = 0; i < ECRNX_HWQ_NB; i++) { + for (j = 0; j < nx_txuser_cnt[i]; j++) { + struct sk_buff *skb; + while ((skb = (struct sk_buff *)ipc_host_tx_flush(ecrnx_hw->ipc_env, i, j))) { + struct ecrnx_sw_txhdr *sw_txhdr = + ((struct ecrnx_txhdr *)skb->data)->sw_hdr; +#ifndef CONFIG_ECRNX_ESWIN + #ifdef CONFIG_ECRNX_AMSDUS_TX + if (sw_txhdr->desc.host.packet_cnt > 1) { + struct ecrnx_amsdu_txhdr *amsdu_txhdr; + list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) { + dma_unmap_single(ecrnx_hw->dev, amsdu_txhdr->dma_addr, + amsdu_txhdr->map_len, DMA_TO_DEVICE); + dev_kfree_skb_any(amsdu_txhdr->skb); + } + } + #endif + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + dma_unmap_single(ecrnx_hw->dev, sw_txhdr->dma_addr, + sw_txhdr->map_len, DMA_TO_DEVICE); +#endif + skb_pull(skb, sw_txhdr->headroom); +#ifdef CONFIG_ECRNX_SOFTMAC + ieee80211_free_txskb(ecrnx_hw->hw, skb); +#else + dev_kfree_skb_any(skb); +#endif /* CONFIG_ECRNX_SOFTMAC */ + } + } + } +} + +/** + * ecrnx_ipc_tx_pending() - Check if TX pframes are pending at FW level + * + * @ecrnx_hw: Main driver data + */ +bool ecrnx_ipc_tx_pending(struct ecrnx_hw *ecrnx_hw) +{ + return ipc_host_tx_frames_pending(ecrnx_hw->ipc_env); +} + +/** + * ecrnx_error_ind() - %DBG_ERROR_IND message callback + * + * @ecrnx_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 ecrnx_umh_done() function has to be called. + */ +void ecrnx_error_ind(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_elem_var *elem = &ecrnx_hw->dbgdump_elem.buf; + struct dbg_debug_dump_tag *dump = elem->addr; + + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, elem->size, + DMA_FROM_DEVICE); + dev_err(ecrnx_hw->dev, "(type %d): dump received\n", + dump->dbg_info.error_type); + +#ifdef CONFIG_ECRNX_DEBUGFS + ecrnx_hw->debugfs.trace_prst = true; + ecrnx_trigger_um_helper(&ecrnx_hw->debugfs); +#endif +} + +/** + * ecrnx_umh_done() - Indicate User Mode helper finished + * + * @ecrnx_hw: Main driver data + * + */ +void ecrnx_umh_done(struct ecrnx_hw *ecrnx_hw) +{ + if (!test_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags)) + return; + + /* this assumes error_ind won't trigger before ipc_host_dbginfobuf_push + is called and so does not irq protect (TODO) against error_ind */ +#ifdef CONFIG_ECRNX_DEBUGFS + ecrnx_hw->debugfs.trace_prst = false; +#ifndef CONFIG_ECRNX_ESWIN + ipc_host_dbginfobuf_push(ecrnx_hw->ipc_env, ecrnx_hw->dbgdump_elem.buf.dma_addr); +#endif +#endif +} |