blob: 1689ac3d5c125c81f0b120b024313fe5f7b08c10 [file] [log] [blame]
/*
* Copyright (c) 2011, 2014, 2016-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include <adf_net_types.h> /* ADF_NBUF_EXEMPT_NO_EXEMPTION, etc. */
#include <adf_nbuf.h> /* adf_nbuf_t, etc. */
#include <adf_os_util.h> /* adf_os_assert */
#include <adf_os_lock.h> /* adf_os_spinlock */
#ifdef QCA_COMPUTE_TX_DELAY
#include <adf_os_time.h> /* adf_os_ticks */
#endif
#include <queue.h> /* TAILQ */
#include <ol_htt_tx_api.h> /* htt_tx_desc_id */
#include <ol_txrx_types.h> /* ol_txrx_pdev_t */
#include <ol_tx_desc.h>
#include <ol_txrx_internal.h>
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
#include <ol_txrx_encap.h> /* OL_TX_RESTORE_HDR, etc*/
#endif
#include <ol_txrx.h>
#ifdef QCA_COMPUTE_TX_DELAY
static inline void
OL_TX_TIMESTAMP_SET(struct ol_tx_desc_t *tx_desc)
{
tx_desc->entry_timestamp_ticks = adf_os_ticks();
}
#else
#define OL_TX_TIMESTAMP_SET(tx_desc) /* no-op */
#endif
static inline struct ol_tx_desc_t *
ol_tx_desc_alloc(struct ol_txrx_pdev_t *pdev, struct ol_txrx_vdev_t *vdev)
{
struct ol_tx_desc_t *tx_desc = NULL;
adf_os_spin_lock_bh(&pdev->tx_mutex);
if (pdev->tx_desc.freelist) {
pdev->tx_desc.num_free--;
tx_desc = &pdev->tx_desc.freelist->tx_desc;
pdev->tx_desc.freelist = pdev->tx_desc.freelist->next;
#ifdef QCA_SUPPORT_TXDESC_SANITY_CHECKS
if (tx_desc->pkt_type != ol_tx_frm_freed
#ifdef QCA_COMPUTE_TX_DELAY
|| tx_desc->entry_timestamp_ticks != 0xffffffff
#endif
) {
TXRX_PRINT(TXRX_PRINT_LEVEL_ERR,
"%s Potential tx_desc corruption pkt_type:0x%x pdev:0x%pK",
__func__, tx_desc->pkt_type, pdev);
#ifdef QCA_COMPUTE_TX_DELAY
TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "%s Timestamp:0x%x\n",
__func__, tx_desc->entry_timestamp_ticks);
#endif
adf_os_assert(0);
}
#endif
}
adf_os_spin_unlock_bh(&pdev->tx_mutex);
if (!tx_desc) {
return NULL;
}
tx_desc->vdev = vdev;
tx_desc->vdev_id = vdev->vdev_id;
#if defined(CONFIG_PER_VDEV_TX_DESC_POOL)
adf_os_atomic_inc(&vdev->tx_desc_count);
#endif
OL_TX_TIMESTAMP_SET(tx_desc);
return tx_desc;
}
static inline struct ol_tx_desc_t *
ol_tx_desc_alloc_hl(struct ol_txrx_pdev_t *pdev, struct ol_txrx_vdev_t *vdev)
{
struct ol_tx_desc_t *tx_desc;
tx_desc = ol_tx_desc_alloc(pdev, vdev);
if (!tx_desc) return NULL;
adf_os_atomic_dec(&pdev->tx_queue.rsrc_cnt);
return tx_desc;
}
#ifdef WLAN_FEATURE_DSRC
static inline void
__ol_tx_desc_free(struct ol_txrx_pdev_t *pdev, struct ol_tx_desc_t *tx_desc)
{
((union ol_tx_desc_list_elem_t *)tx_desc)->next = NULL;
if (pdev->tx_desc.freelist) {
pdev->tx_desc.last->next =
(union ol_tx_desc_list_elem_t *)tx_desc;
} else {
pdev->tx_desc.freelist =
(union ol_tx_desc_list_elem_t *)tx_desc;
}
pdev->tx_desc.last = (union ol_tx_desc_list_elem_t *)tx_desc;
}
#else
static inline void
__ol_tx_desc_free(struct ol_txrx_pdev_t *pdev, struct ol_tx_desc_t *tx_desc)
{
((union ol_tx_desc_list_elem_t *)tx_desc)->next =
pdev->tx_desc.freelist;
pdev->tx_desc.freelist = (union ol_tx_desc_list_elem_t *)tx_desc;
}
#endif /* WLAN_FEATURE_DSRC */
void
ol_tx_desc_free(struct ol_txrx_pdev_t *pdev, struct ol_tx_desc_t *tx_desc)
{
adf_os_spin_lock_bh(&pdev->tx_mutex);
#ifdef QCA_SUPPORT_TXDESC_SANITY_CHECKS
tx_desc->pkt_type = ol_tx_frm_freed;
#ifdef QCA_COMPUTE_TX_DELAY
tx_desc->entry_timestamp_ticks = 0xffffffff;
#endif
#endif
__ol_tx_desc_free(pdev, tx_desc);
pdev->tx_desc.num_free++;
#if defined(CONFIG_PER_VDEV_TX_DESC_POOL)
if (tx_desc->vdev) {
#ifdef QCA_LL_TX_FLOW_CT
if ( (adf_os_atomic_read(&tx_desc->vdev->os_q_paused)) &&
(adf_os_atomic_read(&tx_desc->vdev->tx_desc_count) <
TXRX_HL_TX_FLOW_CTRL_VDEV_LOW_WATER_MARK) ) {
/* wakeup netif_queue */
adf_os_atomic_set(&tx_desc->vdev->os_q_paused, 0);
tx_desc->vdev->osif_flow_control_cb(tx_desc->vdev->osif_dev,
tx_desc->vdev_id, A_TRUE);
}
#endif /* QCA_LL_TX_FLOW_CT */
adf_os_atomic_dec(&tx_desc->vdev->tx_desc_count);
} else {
TXRX_PRINT(TXRX_PRINT_LEVEL_INFO2,
"%s warning: the vdev is referred by tx_desc (%pK) "
"has been detached.\n",
__func__, tx_desc);
}
#endif
#if defined(CONFIG_HL_SUPPORT)
tx_desc->vdev = NULL;
tx_desc->vdev_id = OL_TXRX_INVALID_VDEV_ID;
#endif
adf_os_spin_unlock_bh(&pdev->tx_mutex);
}
struct ol_tx_desc_t *
ol_tx_desc_ll(
struct ol_txrx_pdev_t *pdev,
struct ol_txrx_vdev_t *vdev,
adf_nbuf_t netbuf,
struct ol_txrx_msdu_info_t *msdu_info)
{
struct ol_tx_desc_t *tx_desc;
unsigned int i;
u_int32_t num_frags;
msdu_info->htt.info.vdev_id = vdev->vdev_id;
msdu_info->htt.action.cksum_offload = adf_nbuf_get_tx_cksum(netbuf);
switch (adf_nbuf_get_exemption_type(netbuf)) {
case ADF_NBUF_EXEMPT_NO_EXEMPTION:
case ADF_NBUF_EXEMPT_ON_KEY_MAPPING_KEY_UNAVAILABLE:
/* We want to encrypt this frame */
msdu_info->htt.action.do_encrypt = 1;
break;
case ADF_NBUF_EXEMPT_ALWAYS:
/* We don't want to encrypt this frame */
msdu_info->htt.action.do_encrypt = 0;
break;
default:
adf_os_assert(0);
break;
}
/* allocate the descriptor */
tx_desc = ol_tx_desc_alloc(pdev, vdev);
if (!tx_desc) return NULL;
/* initialize the SW tx descriptor */
tx_desc->netbuf = netbuf;
/* fix this - get pkt_type from msdu_info */
tx_desc->pkt_type = ol_tx_frm_std;
/* initialize the HW tx descriptor */
htt_tx_desc_init(
pdev->htt_pdev, tx_desc->htt_tx_desc,
tx_desc->htt_tx_desc_paddr,
ol_tx_desc_id(pdev, tx_desc),
netbuf,
&msdu_info->htt, NULL, vdev->opmode == wlan_op_mode_ocb);
/*
* Initialize the fragmentation descriptor.
* Skip the prefix fragment (HTT tx descriptor) that was added
* during the call to htt_tx_desc_init above.
*/
num_frags = adf_nbuf_get_num_frags(netbuf);
/* num_frags are expected to be 2 max */
num_frags = (num_frags > CVG_NBUF_MAX_EXTRA_FRAGS) ? CVG_NBUF_MAX_EXTRA_FRAGS : num_frags;
htt_tx_desc_num_frags(pdev->htt_pdev, tx_desc->htt_tx_desc, num_frags-1);
for (i = 1; i < num_frags; i++) {
adf_os_size_t frag_len;
u_int32_t frag_paddr;
frag_len = adf_nbuf_get_frag_len(netbuf, i);
frag_paddr = adf_nbuf_get_frag_paddr_lo(netbuf, i);
htt_tx_desc_frag(
pdev->htt_pdev, tx_desc->htt_tx_desc, i-1, frag_paddr, frag_len);
}
return tx_desc;
}
struct ol_tx_desc_t *
ol_tx_desc_hl(
struct ol_txrx_pdev_t *pdev,
struct ol_txrx_vdev_t *vdev,
adf_nbuf_t netbuf,
struct ol_txrx_msdu_info_t *msdu_info)
{
struct ol_tx_desc_t *tx_desc;
/* FIX THIS: these inits should probably be done by tx classify */
msdu_info->htt.info.vdev_id = vdev->vdev_id;
msdu_info->htt.info.frame_type = pdev->htt_pkt_type;
msdu_info->htt.action.cksum_offload = adf_nbuf_get_tx_cksum(netbuf);
switch (adf_nbuf_get_exemption_type(netbuf)) {
case ADF_NBUF_EXEMPT_NO_EXEMPTION:
case ADF_NBUF_EXEMPT_ON_KEY_MAPPING_KEY_UNAVAILABLE:
/* We want to encrypt this frame */
msdu_info->htt.action.do_encrypt = 1;
break;
case ADF_NBUF_EXEMPT_ALWAYS:
/* We don't want to encrypt this frame */
msdu_info->htt.action.do_encrypt = 0;
break;
default:
adf_os_assert(0);
break;
}
/* allocate the descriptor */
tx_desc = ol_tx_desc_alloc_hl(pdev, vdev);
if (!tx_desc) return NULL;
/* initialize the SW tx descriptor */
tx_desc->netbuf = netbuf;
/* fix this - get pkt_type from msdu_info */
tx_desc->pkt_type = ol_tx_frm_std;
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
tx_desc->orig_l2_hdr_bytes = 0;
#endif
/* the HW tx descriptor will be initialized later by the caller */
return tx_desc;
}
void ol_tx_desc_frame_list_free(
struct ol_txrx_pdev_t *pdev,
ol_tx_desc_list *tx_descs,
int had_error)
{
struct ol_tx_desc_t *tx_desc, *tmp;
adf_nbuf_t msdus = NULL;
TAILQ_FOREACH_SAFE(tx_desc, tx_descs, tx_desc_list_elem, tmp) {
adf_nbuf_t msdu = tx_desc->netbuf;
adf_os_atomic_init(&tx_desc->ref_cnt); /* clear the ref cnt */
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
OL_TX_RESTORE_HDR(tx_desc, msdu); /* restore original hdr offset */
#endif
adf_nbuf_unmap(pdev->osdev, msdu, ADF_OS_DMA_TO_DEVICE);
/* free the tx desc */
ol_tx_desc_free(pdev, tx_desc);
/* link the netbuf into a list to free as a batch */
adf_nbuf_set_next(msdu, msdus);
msdus = msdu;
}
/* free the netbufs as a batch */
adf_nbuf_tx_free(msdus, had_error);
}
void ol_tx_desc_frame_free_nonstd(
struct ol_txrx_pdev_t *pdev,
struct ol_tx_desc_t *tx_desc,
int had_error)
{
int mgmt_type;
ol_txrx_mgmt_tx_cb ota_ack_cb;
char *trace_str;
adf_os_atomic_init(&tx_desc->ref_cnt); /* clear the ref cnt */
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
OL_TX_RESTORE_HDR(tx_desc, (tx_desc->netbuf)); /* restore original hdr offset */
#endif
trace_str = (had_error) ? "OT:C:F:" : "OT:C:S:";
adf_nbuf_trace_update(tx_desc->netbuf, trace_str);
if (tx_desc->pkt_type == ol_tx_frm_no_free) {
/* free the tx desc but don't unmap or free the frame */
if (pdev->tx_data_callback.func) {
adf_nbuf_set_next(tx_desc->netbuf, NULL);
pdev->tx_data_callback.func(
pdev->tx_data_callback.ctxt, tx_desc->netbuf, had_error);
ol_tx_desc_free(pdev, tx_desc);
return;
}
/* let the code below unmap and free the frame */
}
adf_nbuf_unmap(pdev->osdev, tx_desc->netbuf, ADF_OS_DMA_TO_DEVICE);
/* check the frame type to see what kind of special steps are needed */
if (tx_desc->pkt_type == ol_tx_frm_tso) {
#if 0
/*
* Free the segment's customized ethernet+IP+TCP header.
* Fragment 0 added by the WLAN driver is the HTT+HTC tx descriptor.
* Fragment 1 added by the WLAN driver is the Ethernet+IP+TCP header
* added for this TSO segment.
*/
tso_tcp_hdr = adf_nbuf_get_frag_vaddr(tx_desc->netbuf, 1);
ol_tx_tso_hdr_free(pdev, tso_tcp_hdr);
#endif
/* free the netbuf */
adf_nbuf_set_next(tx_desc->netbuf, NULL);
adf_nbuf_tx_free(tx_desc->netbuf, had_error);
} else if ((tx_desc->pkt_type >= OL_TXRX_MGMT_TYPE_BASE) &&
(tx_desc->pkt_type != ol_tx_frm_freed)) {
/* FIX THIS -
* The FW currently has trouble using the host's fragments table
* for management frames. Until this is fixed, rather than
* specifying the fragment table to the FW, the host SW will
* specify just the address of the initial fragment.
* Now that the mgmt frame is done, the HTT tx desc's frags table
* pointer needs to be reset.
*/
htt_tx_desc_frags_table_set(pdev->htt_pdev, tx_desc->htt_tx_desc, 0, 1);
mgmt_type = tx_desc->pkt_type - OL_TXRX_MGMT_TYPE_BASE;
/*
* we already checked the value when the mgmt frame was provided to the txrx layer.
* no need to check it a 2nd time.
*/
ota_ack_cb = pdev->tx_mgmt.callbacks[mgmt_type].ota_ack_cb;
if (ota_ack_cb) {
void *ctxt;
ctxt = pdev->tx_mgmt.callbacks[mgmt_type].ctxt;
ota_ack_cb(ctxt, tx_desc->netbuf, had_error);
}
/* free the netbuf */
adf_nbuf_free(tx_desc->netbuf);
} else {
/* single regular frame */
adf_nbuf_set_next(tx_desc->netbuf, NULL);
adf_nbuf_tx_free(tx_desc->netbuf, had_error);
}
/* free the tx desc */
ol_tx_desc_free(pdev, tx_desc);
}