blob: f7e4c5fd6cecc990844931d9a5c7171806a9fa0f [file] [log] [blame]
/*
* Copyright (c) 2011-2019 The Linux Foundation. All rights reserved.
*
* 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.
*/
/*=== includes ===*/
/* header files for OS primitives */
#include <osdep.h> /* uint32_t, etc. */
#include <qdf_mem.h> /* qdf_mem_malloc,free */
#include <qdf_types.h> /* qdf_device_t, qdf_print */
#include <qdf_lock.h> /* qdf_spinlock */
#include <qdf_atomic.h> /* qdf_atomic_read */
#include <qdf_debugfs.h>
/* header files for utilities */
#include <cds_queue.h> /* TAILQ */
/* header files for configuration API */
#include <ol_cfg.h> /* ol_cfg_is_high_latency */
#include <ol_if_athvar.h>
/* header files for HTT API */
#include <ol_htt_api.h>
#include <ol_htt_tx_api.h>
/* header files for our own APIs */
#include <ol_txrx_api.h>
#include <ol_txrx_dbg.h>
#include <cdp_txrx_ocb.h>
#include <ol_txrx_ctrl_api.h>
#include <cdp_txrx_stats.h>
#include <ol_txrx_osif_api.h>
/* header files for our internal definitions */
#include <ol_txrx_internal.h> /* TXRX_ASSERT, etc. */
#include <wdi_event.h> /* WDI events */
#include <ol_tx.h> /* ol_tx_ll */
#include <ol_rx.h> /* ol_rx_deliver */
#include <ol_txrx_peer_find.h> /* ol_txrx_peer_find_attach, etc. */
#include <ol_rx_pn.h> /* ol_rx_pn_check, etc. */
#include <ol_rx_fwd.h> /* ol_rx_fwd_check, etc. */
#include <ol_rx_reorder_timeout.h> /* OL_RX_REORDER_TIMEOUT_INIT, etc. */
#include <ol_rx_reorder.h>
#include <ol_tx_send.h> /* ol_tx_discard_target_frms */
#include <ol_tx_desc.h> /* ol_tx_desc_frame_free */
#include <ol_tx_queue.h>
#include <ol_tx_sched.h> /* ol_tx_sched_attach, etc. */
#include <ol_txrx.h>
#include <ol_txrx_types.h>
#include <ol_cfg.h>
#include <cdp_txrx_flow_ctrl_legacy.h>
#include <cdp_txrx_cmn_reg.h>
#include <cdp_txrx_bus.h>
#include <cdp_txrx_ipa.h>
#include <cdp_txrx_pmf.h>
#include "wma.h"
#include "hif.h"
#include "hif_main.h"
#include <cdp_txrx_peer_ops.h>
#ifndef REMOVE_PKT_LOG
#include "pktlog_ac.h"
#endif
#include <wlan_policy_mgr_api.h>
#include "epping_main.h"
#include <a_types.h>
#include <cdp_txrx_handle.h>
#include "wlan_qct_sys.h"
#include <htt_internal.h>
#include <ol_txrx_ipa.h>
#include "wlan_roam_debug.h"
#include "cfg_ucfg_api.h"
#define DPT_DEBUGFS_PERMS (QDF_FILE_USR_READ | \
QDF_FILE_USR_WRITE | \
QDF_FILE_GRP_READ | \
QDF_FILE_OTH_READ)
#define DPT_DEBUGFS_NUMBER_BASE 10
/**
* enum dpt_set_param_debugfs - dpt set params
* @DPT_SET_PARAM_PROTO_BITMAP : set proto bitmap
* @DPT_SET_PARAM_NR_RECORDS: set num of records
* @DPT_SET_PARAM_VERBOSITY: set verbosity
*/
enum dpt_set_param_debugfs {
DPT_SET_PARAM_PROTO_BITMAP = 1,
DPT_SET_PARAM_NR_RECORDS = 2,
DPT_SET_PARAM_VERBOSITY = 3,
DPT_SET_PARAM_NUM_RECORDS_TO_DUMP = 4,
DPT_SET_PARAM_MAX,
};
QDF_STATUS ol_txrx_peer_state_update(struct cdp_pdev *pdev,
uint8_t *peer_mac,
enum ol_txrx_peer_state state);
static void ol_vdev_rx_set_intrabss_fwd(struct cdp_vdev *vdev,
bool val);
int ol_txrx_get_tx_pending(struct cdp_pdev *pdev_handle);
extern void
ol_txrx_set_wmm_param(struct cdp_pdev *data_pdev,
struct ol_tx_wmm_param_t wmm_param);
extern void ol_txrx_get_pn_info(void *ppeer, uint8_t **last_pn_valid,
uint64_t **last_pn, uint32_t **rmf_pn_replays);
/* thresh for peer's cached buf queue beyond which the elements are dropped */
#define OL_TXRX_CACHED_BUFQ_THRESH 128
/**
* ol_tx_mark_first_wakeup_packet() - set flag to indicate that
* fw is compatible for marking first packet after wow wakeup
* @value: 1 for enabled/ 0 for disabled
*
* Return: None
*/
static void ol_tx_mark_first_wakeup_packet(uint8_t value)
{
struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
htt_mark_first_wakeup_packet(pdev->htt_pdev, value);
}
/**
* ol_tx_set_is_mgmt_over_wmi_enabled() - set flag to indicate that mgmt over
* wmi is enabled or not.
* @value: 1 for enabled/ 0 for disable
*
* Return: None
*/
void ol_tx_set_is_mgmt_over_wmi_enabled(uint8_t value)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
pdev->is_mgmt_over_wmi_enabled = value;
}
/**
* ol_tx_get_is_mgmt_over_wmi_enabled() - get value of is_mgmt_over_wmi_enabled
*
* Return: is_mgmt_over_wmi_enabled
*/
uint8_t ol_tx_get_is_mgmt_over_wmi_enabled(void)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return 0;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return 0;
}
return pdev->is_mgmt_over_wmi_enabled;
}
#ifdef QCA_SUPPORT_TXRX_LOCAL_PEER_ID
static void *
ol_txrx_find_peer_by_addr_and_vdev(struct cdp_pdev *ppdev,
struct cdp_vdev *pvdev, uint8_t *peer_addr, uint8_t *peer_id)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
struct ol_txrx_peer_t *peer;
peer = ol_txrx_peer_vdev_find_hash(pdev, vdev, peer_addr, 0, 1);
if (!peer)
return NULL;
*peer_id = peer->local_id;
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_INTERNAL);
return peer;
}
static QDF_STATUS ol_txrx_get_vdevid(void *ppeer, uint8_t *vdev_id)
{
struct ol_txrx_peer_t *peer = ppeer;
if (!peer) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"peer argument is null!!");
return QDF_STATUS_E_FAILURE;
}
*vdev_id = peer->vdev->vdev_id;
return QDF_STATUS_SUCCESS;
}
ol_txrx_vdev_handle
ol_txrx_get_vdev_by_peer_addr(struct cdp_pdev *ppdev,
struct qdf_mac_addr peer_addr)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
struct ol_txrx_peer_t *peer = NULL;
ol_txrx_vdev_handle vdev;
/* peer_id to be removed PEER_ID_CLEANUP */
uint8_t peer_id;
if (!pdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_HIGH,
"PDEV not found for peer_addr: " QDF_MAC_ADDR_STR,
QDF_MAC_ADDR_ARRAY(peer_addr.bytes));
return NULL;
}
peer = ol_txrx_peer_get_ref_by_addr(pdev, peer_addr.bytes, &peer_id,
PEER_DEBUG_ID_OL_INTERNAL);
if (!peer) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_HIGH,
"PDEV not found for peer_addr:" QDF_MAC_ADDR_STR,
QDF_MAC_ADDR_ARRAY(peer_addr.bytes));
return NULL;
}
vdev = peer->vdev;
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_INTERNAL);
return vdev;
}
/**
* ol_txrx_wrapper_get_vdev_by_peer_addr() - Get vdev handle by peer mac address
* @ppdev - data path device instance
* @peer_addr - peer mac address
*
* Get virtual interface handle by local peer mac address
*
* Return: Virtual interface instance handle
* NULL in case cannot find
*/
static struct cdp_vdev *
ol_txrx_wrapper_get_vdev_by_peer_addr(struct cdp_pdev *ppdev,
struct qdf_mac_addr peer_addr)
{
return (struct cdp_vdev *)ol_txrx_get_vdev_by_peer_addr(ppdev,
peer_addr);
}
/**
* ol_txrx_find_peer_by_addr() - find peer via peer mac addr and peer_id
* @ppdev: pointer of type cdp_pdev
* @peer_addr: peer mac addr
* @peer_id: pointer to fill in the value of peer->local_id for caller
*
* This function finds a peer with given mac address and returns its peer_id.
* Note that this function does not increment the peer->ref_cnt.
* This means that the peer may be deleted in some other parallel context after
* its been found.
*
* Return: peer handle if peer is found, NULL if peer is not found.
*/
void *ol_txrx_find_peer_by_addr(struct cdp_pdev *ppdev,
uint8_t *peer_addr,
uint8_t *peer_id)
{
struct ol_txrx_peer_t *peer;
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
peer = ol_txrx_peer_find_hash_find_get_ref(pdev, peer_addr, 0, 1,
PEER_DEBUG_ID_OL_INTERNAL);
if (!peer)
return NULL;
*peer_id = peer->local_id;
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_INTERNAL);
return peer;
}
/**
* ol_txrx_peer_get_ref_by_addr() - get peer ref via peer mac addr and peer_id
* @pdev: pointer of type ol_txrx_pdev_handle
* @peer_addr: peer mac addr
* @peer_id: pointer to fill in the value of peer->local_id for caller
*
* This function finds the peer with given mac address and returns its peer_id.
* Note that this function increments the peer->ref_cnt.
* This makes sure that peer will be valid. This also means the caller needs to
* call the corresponding API - ol_txrx_peer_release_ref to delete the peer
* reference.
* Sample usage:
* {
* //the API call below increments the peer->ref_cnt
* peer = ol_txrx_peer_get_ref_by_addr(pdev, peer_addr, peer_id, dbg_id);
*
* // Once peer usage is done
*
* //the API call below decrements the peer->ref_cnt
* ol_txrx_peer_release_ref(peer, dbg_id);
* }
*
* Return: peer handle if the peer is found, NULL if peer is not found.
*/
ol_txrx_peer_handle ol_txrx_peer_get_ref_by_addr(ol_txrx_pdev_handle pdev,
u8 *peer_addr,
u8 *peer_id,
enum peer_debug_id_type dbg_id)
{
struct ol_txrx_peer_t *peer;
peer = ol_txrx_peer_find_hash_find_get_ref(pdev, peer_addr, 0, 1,
dbg_id);
if (!peer)
return NULL;
*peer_id = peer->local_id;
return peer;
}
static uint16_t ol_txrx_local_peer_id(void *ppeer)
{
ol_txrx_peer_handle peer = ppeer;
return peer->local_id;
}
/**
* @brief Find a txrx peer handle from a peer's local ID
* @details
* The control SW typically uses the txrx peer handle to refer to the peer.
* In unusual circumstances, if it is infeasible for the control SW maintain
* the txrx peer handle but it can maintain a small integer local peer ID,
* this function allows the peer handled to be retrieved, based on the local
* peer ID.
*
* @param pdev - the data physical device object
* @param local_peer_id - the ID txrx assigned locally to the peer in question
* @return handle to the txrx peer object
*/
ol_txrx_peer_handle
ol_txrx_peer_find_by_local_id(struct cdp_pdev *ppdev,
uint8_t local_peer_id)
{
struct ol_txrx_peer_t *peer;
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
if ((local_peer_id == OL_TXRX_INVALID_LOCAL_PEER_ID) ||
(local_peer_id >= OL_TXRX_NUM_LOCAL_PEER_IDS)) {
return NULL;
}
qdf_spin_lock_bh(&pdev->local_peer_ids.lock);
peer = pdev->local_peer_ids.map[local_peer_id];
qdf_spin_unlock_bh(&pdev->local_peer_ids.lock);
return peer;
}
/**
* @brief Find a txrx peer handle from a peer's local ID
* @param pdev - the data physical device object
* @param local_peer_id - the ID txrx assigned locally to the peer in question
* @dbg_id - debug_id to track caller
* @return handle to the txrx peer object
* @details
* The control SW typically uses the txrx peer handle to refer to the peer.
* In unusual circumstances, if it is infeasible for the control SW maintain
* the txrx peer handle but it can maintain a small integer local peer ID,
* this function allows the peer handled to be retrieved, based on the local
* peer ID.
*
* Note that this function increments the peer->ref_cnt.
* This makes sure that peer will be valid. This also means the caller needs to
* call the corresponding API -
* ol_txrx_peer_release_ref
*
* reference.
* Sample usage:
* {
* //the API call below increments the peer->ref_cnt
* peer = ol_txrx_peer_get_ref_by_local_id(pdev,local_peer_id, dbg_id);
*
* // Once peer usage is done
*
* //the API call below decrements the peer->ref_cnt
* ol_txrx_peer_release_ref(peer, dbg_id);
* }
*
* Return: peer handle if the peer is found, NULL if peer is not found.
*/
ol_txrx_peer_handle
ol_txrx_peer_get_ref_by_local_id(struct cdp_pdev *ppdev,
uint8_t local_peer_id,
enum peer_debug_id_type dbg_id)
{
struct ol_txrx_peer_t *peer = NULL;
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
if ((local_peer_id == OL_TXRX_INVALID_LOCAL_PEER_ID) ||
(local_peer_id >= OL_TXRX_NUM_LOCAL_PEER_IDS)) {
return NULL;
}
qdf_spin_lock_bh(&pdev->peer_ref_mutex);
qdf_spin_lock_bh(&pdev->local_peer_ids.lock);
peer = pdev->local_peer_ids.map[local_peer_id];
qdf_spin_unlock_bh(&pdev->local_peer_ids.lock);
if (peer && peer->valid)
ol_txrx_peer_get_ref(peer, dbg_id);
else
peer = NULL;
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
return peer;
}
static void ol_txrx_local_peer_id_pool_init(struct ol_txrx_pdev_t *pdev)
{
int i;
/* point the freelist to the first ID */
pdev->local_peer_ids.freelist = 0;
/* link each ID to the next one */
for (i = 0; i < OL_TXRX_NUM_LOCAL_PEER_IDS; i++) {
pdev->local_peer_ids.pool[i] = i + 1;
pdev->local_peer_ids.map[i] = NULL;
}
/* link the last ID to itself, to mark the end of the list */
i = OL_TXRX_NUM_LOCAL_PEER_IDS;
pdev->local_peer_ids.pool[i] = i;
qdf_spinlock_create(&pdev->local_peer_ids.lock);
}
static void
ol_txrx_local_peer_id_alloc(struct ol_txrx_pdev_t *pdev,
struct ol_txrx_peer_t *peer)
{
int i;
qdf_spin_lock_bh(&pdev->local_peer_ids.lock);
i = pdev->local_peer_ids.freelist;
if (pdev->local_peer_ids.pool[i] == i) {
/* the list is empty, except for the list-end marker */
peer->local_id = OL_TXRX_INVALID_LOCAL_PEER_ID;
} else {
/* take the head ID and advance the freelist */
peer->local_id = i;
pdev->local_peer_ids.freelist = pdev->local_peer_ids.pool[i];
pdev->local_peer_ids.map[i] = peer;
}
qdf_spin_unlock_bh(&pdev->local_peer_ids.lock);
}
static void
ol_txrx_local_peer_id_free(struct ol_txrx_pdev_t *pdev,
struct ol_txrx_peer_t *peer)
{
int i = peer->local_id;
if ((i == OL_TXRX_INVALID_LOCAL_PEER_ID) ||
(i >= OL_TXRX_NUM_LOCAL_PEER_IDS)) {
return;
}
/* put this ID on the head of the freelist */
qdf_spin_lock_bh(&pdev->local_peer_ids.lock);
pdev->local_peer_ids.pool[i] = pdev->local_peer_ids.freelist;
pdev->local_peer_ids.freelist = i;
pdev->local_peer_ids.map[i] = NULL;
qdf_spin_unlock_bh(&pdev->local_peer_ids.lock);
}
static void ol_txrx_local_peer_id_cleanup(struct ol_txrx_pdev_t *pdev)
{
qdf_spinlock_destroy(&pdev->local_peer_ids.lock);
}
#else
#define ol_txrx_local_peer_id_pool_init(pdev) /* no-op */
#define ol_txrx_local_peer_id_alloc(pdev, peer) /* no-op */
#define ol_txrx_local_peer_id_free(pdev, peer) /* no-op */
#define ol_txrx_local_peer_id_cleanup(pdev) /* no-op */
#endif
#if defined(CONFIG_DP_TRACE) && defined(WLAN_DEBUGFS)
/**
* ol_txrx_read_dpt_buff_debugfs() - read dp trace buffer
* @file: file to read
* @arg: pdev object
*
* Return: QDF_STATUS
*/
static QDF_STATUS ol_txrx_read_dpt_buff_debugfs(qdf_debugfs_file_t file,
void *arg)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)arg;
uint32_t i = 0;
QDF_STATUS status = QDF_STATUS_SUCCESS;
if (pdev->state == QDF_DPT_DEBUGFS_STATE_SHOW_STATE_INVALID)
return QDF_STATUS_E_INVAL;
else if (pdev->state == QDF_DPT_DEBUGFS_STATE_SHOW_COMPLETE) {
pdev->state = QDF_DPT_DEBUGFS_STATE_SHOW_STATE_INIT;
return QDF_STATUS_SUCCESS;
}
i = qdf_dpt_get_curr_pos_debugfs(file, pdev->state);
status = qdf_dpt_dump_stats_debugfs(file, i);
if (status == QDF_STATUS_E_FAILURE)
pdev->state = QDF_DPT_DEBUGFS_STATE_SHOW_IN_PROGRESS;
else if (status == QDF_STATUS_SUCCESS)
pdev->state = QDF_DPT_DEBUGFS_STATE_SHOW_COMPLETE;
return status;
}
/**
* ol_txrx_conv_str_to_int_debugfs() - convert string to int
* @buf: buffer containing string
* @len: buffer len
* @proto_bitmap: defines the protocol to be tracked
* @nr_records: defines the nth packet which is traced
* @verbosity: defines the verbosity level
*
* This function expects char buffer to be null terminated.
* Otherwise results could be unexpected values.
*
* Return: 0 on success
*/
static int ol_txrx_conv_str_to_int_debugfs(char *buf, qdf_size_t len,
int *proto_bitmap,
int *nr_records,
int *verbosity,
int *num_records_to_dump)
{
int num_value = DPT_SET_PARAM_PROTO_BITMAP;
int ret, param_value = 0;
char *buf_param = buf;
int i;
for (i = 1; i < DPT_SET_PARAM_MAX; i++) {
/* Loop till you reach space as kstrtoint operates till
* null character. Replace space with null character
* to read each value.
* terminate the loop either at null terminated char or
* len is 0.
*/
while (*buf && len) {
if (*buf == ' ') {
*buf = '\0';
buf++;
len--;
break;
}
buf++;
len--;
}
/* get the parameter */
ret = qdf_kstrtoint(buf_param,
DPT_DEBUGFS_NUMBER_BASE,
&param_value);
if (ret) {
QDF_TRACE(QDF_MODULE_ID_TXRX,
QDF_TRACE_LEVEL_ERROR,
"%s: Error while parsing buffer. ret %d",
__func__, ret);
return ret;
}
switch (num_value) {
case DPT_SET_PARAM_PROTO_BITMAP:
*proto_bitmap = param_value;
break;
case DPT_SET_PARAM_NR_RECORDS:
*nr_records = param_value;
break;
case DPT_SET_PARAM_VERBOSITY:
*verbosity = param_value;
break;
case DPT_SET_PARAM_NUM_RECORDS_TO_DUMP:
if (param_value > MAX_QDF_DP_TRACE_RECORDS)
param_value = MAX_QDF_DP_TRACE_RECORDS;
*num_records_to_dump = param_value;
break;
default:
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s %d: :Set command needs exactly 4 arguments in format <proto_bitmap> <number of record> <Verbosity> <number of records to dump>.",
__func__, __LINE__);
break;
}
num_value++;
/*buf_param should now point to the next param value. */
buf_param = buf;
}
/* buf is not yet NULL implies more than 4 params are passed. */
if (*buf) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s %d: :Set command needs exactly 4 arguments in format <proto_bitmap> <number of record> <Verbosity> <number of records to dump>.",
__func__, __LINE__);
return -EINVAL;
}
return 0;
}
/**
* ol_txrx_write_dpt_buff_debugfs() - set dp trace parameters
* @priv: pdev object
* @buf: buff to get value for dpt parameters
* @len: buf length
*
* Return: QDF_STATUS
*/
static QDF_STATUS ol_txrx_write_dpt_buff_debugfs(void *priv,
const char *buf,
qdf_size_t len)
{
int ret;
int proto_bitmap = 0;
int nr_records = 0;
int verbosity = 0;
int num_records_to_dump = 0;
char *buf1 = NULL;
if (!buf || !len) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: null buffer or len. len %u",
__func__, (uint8_t)len);
return QDF_STATUS_E_FAULT;
}
buf1 = (char *)qdf_mem_malloc(len);
if (!buf1)
return QDF_STATUS_E_FAULT;
qdf_mem_copy(buf1, buf, len);
ret = ol_txrx_conv_str_to_int_debugfs(buf1, len, &proto_bitmap,
&nr_records, &verbosity,
&num_records_to_dump);
if (ret) {
qdf_mem_free(buf1);
return QDF_STATUS_E_INVAL;
}
qdf_dpt_set_value_debugfs(proto_bitmap, nr_records, verbosity,
num_records_to_dump);
qdf_mem_free(buf1);
return QDF_STATUS_SUCCESS;
}
static int ol_txrx_debugfs_init(struct ol_txrx_pdev_t *pdev)
{
pdev->dpt_debugfs_fops.show = ol_txrx_read_dpt_buff_debugfs;
pdev->dpt_debugfs_fops.write = ol_txrx_write_dpt_buff_debugfs;
pdev->dpt_debugfs_fops.priv = pdev;
pdev->dpt_stats_log_dir = qdf_debugfs_create_dir("dpt_stats", NULL);
if (!pdev->dpt_stats_log_dir) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: error while creating debugfs dir for %s",
__func__, "dpt_stats");
pdev->state = QDF_DPT_DEBUGFS_STATE_SHOW_STATE_INVALID;
return -EBUSY;
}
if (!qdf_debugfs_create_file("dump_set_dpt_logs", DPT_DEBUGFS_PERMS,
pdev->dpt_stats_log_dir,
&pdev->dpt_debugfs_fops)) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: debug Entry creation failed!",
__func__);
pdev->state = QDF_DPT_DEBUGFS_STATE_SHOW_STATE_INVALID;
return -EBUSY;
}
pdev->state = QDF_DPT_DEBUGFS_STATE_SHOW_STATE_INIT;
return 0;
}
static void ol_txrx_debugfs_exit(ol_txrx_pdev_handle pdev)
{
qdf_debugfs_remove_dir_recursive(pdev->dpt_stats_log_dir);
}
#else
static inline int ol_txrx_debugfs_init(struct ol_txrx_pdev_t *pdev)
{
return 0;
}
static inline void ol_txrx_debugfs_exit(ol_txrx_pdev_handle pdev)
{
}
#endif
/**
* ol_txrx_pdev_attach() - allocate txrx pdev
* @soc: soc handle
* @ctrl_pdev: cfg pdev
* @htc_pdev: HTC pdev
* @osdev: os dev
* @pdev_id: pdev identifier for pdev attach
*
* Return: txrx pdev handle
* NULL for failure
*/
static struct cdp_pdev *
ol_txrx_pdev_attach(ol_txrx_soc_handle soc,
struct cdp_ctrl_objmgr_pdev *ctrl_pdev,
HTC_HANDLE htc_pdev, qdf_device_t osdev, uint8_t pdev_id)
{
struct ol_txrx_soc_t *ol_soc = cdp_soc_t_to_ol_txrx_soc_t(soc);
struct ol_txrx_pdev_t *pdev;
struct cdp_cfg *cfg_pdev = (struct cdp_cfg *)ctrl_pdev;
int i, tid;
if (pdev_id == OL_TXRX_INVALID_PDEV_ID)
return NULL;
pdev = qdf_mem_malloc(sizeof(*pdev));
if (!pdev)
goto fail0;
/* init LL/HL cfg here */
pdev->cfg.is_high_latency = ol_cfg_is_high_latency(cfg_pdev);
/*
* Credit reporting through HTT_T2H_MSG_TYPE_TX_CREDIT_UPDATE_IND
* enabled or not.
*/
pdev->cfg.credit_update_enabled =
ol_cfg_is_credit_update_enabled(cfg_pdev);
/* Explicitly request TX Completions from FW */
pdev->cfg.request_tx_comp = cds_is_ptp_rx_opt_enabled() ||
cds_is_packet_log_enabled();
pdev->cfg.default_tx_comp_req = !ol_cfg_tx_free_at_download(cfg_pdev);
/* store provided params */
pdev->ctrl_pdev = cfg_pdev;
pdev->osdev = osdev;
pdev->id = pdev_id;
pdev->soc = ol_soc;
ol_soc->pdev_list[pdev_id] = pdev;
for (i = 0; i < htt_num_sec_types; i++)
pdev->sec_types[i] = (enum ol_sec_type)i;
TXRX_STATS_INIT(pdev);
ol_txrx_tso_stats_init(pdev);
ol_txrx_fw_stats_desc_pool_init(pdev, FW_STATS_DESC_POOL_SIZE);
TAILQ_INIT(&pdev->vdev_list);
TAILQ_INIT(&pdev->req_list);
pdev->req_list_depth = 0;
qdf_spinlock_create(&pdev->req_list_spinlock);
qdf_spinlock_create(&pdev->tx_mutex);
/* do initial set up of the peer ID -> peer object lookup map */
if (ol_txrx_peer_find_attach(pdev))
goto fail1;
/* initialize the counter of the target's tx buffer availability */
qdf_atomic_init(&pdev->target_tx_credit);
qdf_atomic_init(&pdev->orig_target_tx_credit);
qdf_atomic_init(&pdev->pad_reserve_tx_credit);
qdf_atomic_add(1, &pdev->pad_reserve_tx_credit);
if (ol_cfg_is_high_latency(cfg_pdev)) {
qdf_spinlock_create(&pdev->tx_queue_spinlock);
pdev->tx_sched.scheduler = ol_tx_sched_attach(pdev);
if (!pdev->tx_sched.scheduler)
goto fail2;
}
ol_txrx_pdev_txq_log_init(pdev);
ol_txrx_pdev_grp_stats_init(pdev);
pdev->htt_pdev =
htt_pdev_alloc(pdev, cfg_pdev, htc_pdev, osdev);
if (!pdev->htt_pdev)
goto fail3;
htt_register_rx_pkt_dump_callback(pdev->htt_pdev,
ol_rx_pkt_dump_call);
/*
* Init the tid --> category table.
* Regular tids (0-15) map to their AC.
* Extension tids get their own categories.
*/
for (tid = 0; tid < OL_TX_NUM_QOS_TIDS; tid++) {
int ac = TXRX_TID_TO_WMM_AC(tid);
pdev->tid_to_ac[tid] = ac;
}
pdev->tid_to_ac[OL_TX_NON_QOS_TID] =
OL_TX_SCHED_WRR_ADV_CAT_NON_QOS_DATA;
pdev->tid_to_ac[OL_TX_MGMT_TID] =
OL_TX_SCHED_WRR_ADV_CAT_UCAST_MGMT;
pdev->tid_to_ac[OL_TX_NUM_TIDS + OL_TX_VDEV_MCAST_BCAST] =
OL_TX_SCHED_WRR_ADV_CAT_MCAST_DATA;
pdev->tid_to_ac[OL_TX_NUM_TIDS + OL_TX_VDEV_DEFAULT_MGMT] =
OL_TX_SCHED_WRR_ADV_CAT_MCAST_MGMT;
if (ol_cfg_is_flow_steering_enabled(pdev->ctrl_pdev))
pdev->peer_id_unmap_ref_cnt =
TXRX_RFS_ENABLE_PEER_ID_UNMAP_COUNT;
else
pdev->peer_id_unmap_ref_cnt =
TXRX_RFS_DISABLE_PEER_ID_UNMAP_COUNT;
ol_txrx_debugfs_init(pdev);
return (struct cdp_pdev *)pdev;
fail3:
ol_txrx_peer_find_detach(pdev);
fail2:
if (ol_cfg_is_high_latency(cfg_pdev))
qdf_spinlock_destroy(&pdev->tx_queue_spinlock);
fail1:
qdf_spinlock_destroy(&pdev->tx_mutex);
ol_txrx_tso_stats_deinit(pdev);
ol_txrx_fw_stats_desc_pool_deinit(pdev);
qdf_mem_free(pdev);
fail0:
return NULL;
}
#if !defined(REMOVE_PKT_LOG) && !defined(QVIT)
/**
* htt_pkt_log_init() - API to initialize packet log
* @handle: pdev handle
* @scn: HIF context
*
* Return: void
*/
void htt_pkt_log_init(struct cdp_pdev *ppdev, void *scn)
{
struct ol_txrx_pdev_t *handle = (struct ol_txrx_pdev_t *)ppdev;
if (handle->pkt_log_init)
return;
if (cds_get_conparam() != QDF_GLOBAL_FTM_MODE &&
!QDF_IS_EPPING_ENABLED(cds_get_conparam())) {
pktlog_sethandle(&handle->pl_dev, scn);
pktlog_set_callback_regtype(PKTLOG_DEFAULT_CALLBACK_REGISTRATION);
if (pktlogmod_init(scn))
qdf_print(" pktlogmod_init failed");
else
handle->pkt_log_init = true;
}
}
/**
* htt_pktlogmod_exit() - API to cleanup pktlog info
* @handle: Pdev handle
* @scn: HIF Context
*
* Return: void
*/
static void htt_pktlogmod_exit(struct ol_txrx_pdev_t *handle)
{
if (cds_get_conparam() != QDF_GLOBAL_FTM_MODE &&
!QDF_IS_EPPING_ENABLED(cds_get_conparam()) &&
handle->pkt_log_init) {
pktlogmod_exit(handle);
handle->pkt_log_init = false;
}
}
#else
void htt_pkt_log_init(struct cdp_pdev *pdev_handle, void *ol_sc) { }
static void htt_pktlogmod_exit(ol_txrx_pdev_handle handle) { }
#endif
#ifdef QCA_LL_PDEV_TX_FLOW_CONTROL
/**
* ol_txrx_pdev_set_threshold() - set pdev pool stop/start threshold
* @pdev: txrx pdev
*
* Return: void
*/
static void ol_txrx_pdev_set_threshold(struct ol_txrx_pdev_t *pdev)
{
uint32_t stop_threshold;
uint32_t start_threshold;
uint16_t desc_pool_size = pdev->tx_desc.pool_size;
stop_threshold = ol_cfg_get_tx_flow_stop_queue_th(pdev->ctrl_pdev);
start_threshold = stop_threshold +
ol_cfg_get_tx_flow_start_queue_offset(pdev->ctrl_pdev);
pdev->tx_desc.start_th = (start_threshold * desc_pool_size) / 100;
pdev->tx_desc.stop_th = (stop_threshold * desc_pool_size) / 100;
pdev->tx_desc.stop_priority_th =
(TX_PRIORITY_TH * pdev->tx_desc.stop_th) / 100;
if (pdev->tx_desc.stop_priority_th >= MAX_TSO_SEGMENT_DESC)
pdev->tx_desc.stop_priority_th -= MAX_TSO_SEGMENT_DESC;
pdev->tx_desc.start_priority_th =
(TX_PRIORITY_TH * pdev->tx_desc.start_th) / 100;
if (pdev->tx_desc.start_priority_th >= MAX_TSO_SEGMENT_DESC)
pdev->tx_desc.start_priority_th -= MAX_TSO_SEGMENT_DESC;
pdev->tx_desc.status = FLOW_POOL_ACTIVE_UNPAUSED;
}
#else
static inline void ol_txrx_pdev_set_threshold(struct ol_txrx_pdev_t *pdev)
{
}
#endif
/**
* ol_txrx_pdev_post_attach() - attach txrx pdev
* @pdev: txrx pdev
*
* Return: 0 for success
*/
int
ol_txrx_pdev_post_attach(struct cdp_pdev *ppdev)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
uint16_t i;
uint16_t fail_idx = 0;
int ret = 0;
uint16_t desc_pool_size;
struct hif_opaque_softc *osc = cds_get_context(QDF_MODULE_ID_HIF);
uint16_t desc_element_size = sizeof(union ol_tx_desc_list_elem_t);
union ol_tx_desc_list_elem_t *c_element;
unsigned int sig_bit;
uint16_t desc_per_page;
if (!osc) {
ret = -EINVAL;
goto ol_attach_fail;
}
/*
* For LL, limit the number of host's tx descriptors to match
* the number of target FW tx descriptors.
* This simplifies the FW, by ensuring the host will never
* download more tx descriptors than the target has space for.
* The FW will drop/free low-priority tx descriptors when it
* starts to run low, so that in theory the host should never
* run out of tx descriptors.
*/
/*
* LL - initialize the target credit outselves.
* HL - wait for a HTT target credit initialization
* during htt_attach.
*/
desc_pool_size = ol_tx_get_desc_global_pool_size(pdev);
ol_tx_init_pdev(pdev);
ol_tx_desc_dup_detect_init(pdev, desc_pool_size);
ol_tx_setup_fastpath_ce_handles(osc, pdev);
if ((ol_txrx_get_new_htt_msg_format(pdev)))
ol_set_cfg_new_htt_format(pdev->ctrl_pdev, true);
else
ol_set_cfg_new_htt_format(pdev->ctrl_pdev, false);
ret = htt_attach(pdev->htt_pdev, desc_pool_size);
if (ret)
goto htt_attach_fail;
/* Attach micro controller data path offload resource */
if (ol_cfg_ipa_uc_offload_enabled(pdev->ctrl_pdev)) {
ret = htt_ipa_uc_attach(pdev->htt_pdev);
if (ret)
goto uc_attach_fail;
}
/* Calculate single element reserved size power of 2 */
pdev->tx_desc.desc_reserved_size = qdf_get_pwr2(desc_element_size);
qdf_mem_multi_pages_alloc(pdev->osdev, &pdev->tx_desc.desc_pages,
pdev->tx_desc.desc_reserved_size, desc_pool_size, 0, true);
if ((0 == pdev->tx_desc.desc_pages.num_pages) ||
(!pdev->tx_desc.desc_pages.cacheable_pages)) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"Page alloc fail");
ret = -ENOMEM;
goto page_alloc_fail;
}
desc_per_page = pdev->tx_desc.desc_pages.num_element_per_page;
pdev->tx_desc.offset_filter = desc_per_page - 1;
/* Calculate page divider to find page number */
sig_bit = 0;
while (desc_per_page) {
sig_bit++;
desc_per_page = desc_per_page >> 1;
}
pdev->tx_desc.page_divider = (sig_bit - 1);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_DEBUG,
"page_divider 0x%x, offset_filter 0x%x num elem %d, ol desc num page %d, ol desc per page %d",
pdev->tx_desc.page_divider, pdev->tx_desc.offset_filter,
desc_pool_size, pdev->tx_desc.desc_pages.num_pages,
pdev->tx_desc.desc_pages.num_element_per_page);
/*
* Each SW tx desc (used only within the tx datapath SW) has a
* matching HTT tx desc (used for downloading tx meta-data to FW/HW).
* Go ahead and allocate the HTT tx desc and link it with the SW tx
* desc now, to avoid doing it during time-critical transmit.
*/
pdev->tx_desc.pool_size = desc_pool_size;
pdev->tx_desc.freelist =
(union ol_tx_desc_list_elem_t *)
(*pdev->tx_desc.desc_pages.cacheable_pages);
c_element = pdev->tx_desc.freelist;
for (i = 0; i < desc_pool_size; i++) {
void *htt_tx_desc;
void *htt_frag_desc = NULL;
qdf_dma_addr_t frag_paddr = 0;
qdf_dma_addr_t paddr;
if (i == (desc_pool_size - 1))
c_element->next = NULL;
else
c_element->next = (union ol_tx_desc_list_elem_t *)
ol_tx_desc_find(pdev, i + 1);
htt_tx_desc = htt_tx_desc_alloc(pdev->htt_pdev, &paddr, i);
if (!htt_tx_desc) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_FATAL,
"%s: failed to alloc HTT tx desc (%d of %d)",
__func__, i, desc_pool_size);
fail_idx = i;
ret = -ENOMEM;
goto desc_alloc_fail;
}
c_element->tx_desc.htt_tx_desc = htt_tx_desc;
c_element->tx_desc.htt_tx_desc_paddr = paddr;
ret = htt_tx_frag_alloc(pdev->htt_pdev,
i, &frag_paddr, &htt_frag_desc);
if (ret) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: failed to alloc HTT frag dsc (%d/%d)",
__func__, i, desc_pool_size);
/* Is there a leak here, is this handling correct? */
fail_idx = i;
goto desc_alloc_fail;
}
if (!ret && htt_frag_desc) {
/*
* Initialize the first 6 words (TSO flags)
* of the frag descriptor
*/
memset(htt_frag_desc, 0, 6 * sizeof(uint32_t));
c_element->tx_desc.htt_frag_desc = htt_frag_desc;
c_element->tx_desc.htt_frag_desc_paddr = frag_paddr;
}
#ifdef QCA_SUPPORT_TXDESC_SANITY_CHECKS
c_element->tx_desc.pkt_type = 0xff;
#ifdef QCA_COMPUTE_TX_DELAY
c_element->tx_desc.entry_timestamp_ticks =
0xffffffff;
#endif
#endif
c_element->tx_desc.id = i;
qdf_atomic_init(&c_element->tx_desc.ref_cnt);
c_element = c_element->next;
fail_idx = i;
}
/* link SW tx descs into a freelist */
pdev->tx_desc.num_free = desc_pool_size;
ol_txrx_dbg("first tx_desc:0x%pK Last tx desc:0x%pK",
(uint32_t *)pdev->tx_desc.freelist,
(uint32_t *)(pdev->tx_desc.freelist + desc_pool_size));
ol_txrx_pdev_set_threshold(pdev);
/* check what format of frames are expected to be delivered by the OS */
pdev->frame_format = ol_cfg_frame_type(pdev->ctrl_pdev);
if (pdev->frame_format == wlan_frm_fmt_native_wifi)
pdev->htt_pkt_type = htt_pkt_type_native_wifi;
else if (pdev->frame_format == wlan_frm_fmt_802_3) {
if (ol_cfg_is_ce_classify_enabled(pdev->ctrl_pdev))
pdev->htt_pkt_type = htt_pkt_type_eth2;
else
pdev->htt_pkt_type = htt_pkt_type_ethernet;
} else {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s Invalid standard frame type: %d",
__func__, pdev->frame_format);
ret = -EINVAL;
goto control_init_fail;
}
/* setup the global rx defrag waitlist */
TAILQ_INIT(&pdev->rx.defrag.waitlist);
/* configure where defrag timeout and duplicate detection is handled */
pdev->rx.flags.defrag_timeout_check =
pdev->rx.flags.dup_check =
ol_cfg_rx_host_defrag_timeout_duplicate_check(pdev->ctrl_pdev);
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
/* Need to revisit this part. Currently,hardcode to riva's caps */
pdev->target_tx_tran_caps = wlan_frm_tran_cap_raw;
pdev->target_rx_tran_caps = wlan_frm_tran_cap_raw;
/*
* The Riva HW de-aggregate doesn't have capability to generate 802.11
* header for non-first subframe of A-MSDU.
*/
pdev->sw_subfrm_hdr_recovery_enable = 1;
/*
* The Riva HW doesn't have the capability to set Protected Frame bit
* in the MAC header for encrypted data frame.
*/
pdev->sw_pf_proc_enable = 1;
if (pdev->frame_format == wlan_frm_fmt_802_3) {
/*
* sw llc process is only needed in
* 802.3 to 802.11 transform case
*/
pdev->sw_tx_llc_proc_enable = 1;
pdev->sw_rx_llc_proc_enable = 1;
} else {
pdev->sw_tx_llc_proc_enable = 0;
pdev->sw_rx_llc_proc_enable = 0;
}
switch (pdev->frame_format) {
case wlan_frm_fmt_raw:
pdev->sw_tx_encap =
pdev->target_tx_tran_caps & wlan_frm_tran_cap_raw
? 0 : 1;
pdev->sw_rx_decap =
pdev->target_rx_tran_caps & wlan_frm_tran_cap_raw
? 0 : 1;
break;
case wlan_frm_fmt_native_wifi:
pdev->sw_tx_encap =
pdev->
target_tx_tran_caps & wlan_frm_tran_cap_native_wifi
? 0 : 1;
pdev->sw_rx_decap =
pdev->
target_rx_tran_caps & wlan_frm_tran_cap_native_wifi
? 0 : 1;
break;
case wlan_frm_fmt_802_3:
pdev->sw_tx_encap =
pdev->target_tx_tran_caps & wlan_frm_tran_cap_8023
? 0 : 1;
pdev->sw_rx_decap =
pdev->target_rx_tran_caps & wlan_frm_tran_cap_8023
? 0 : 1;
break;
default:
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"Invalid std frame type; [en/de]cap: f:%x t:%x r:%x",
pdev->frame_format,
pdev->target_tx_tran_caps, pdev->target_rx_tran_caps);
ret = -EINVAL;
goto control_init_fail;
}
#endif
/*
* Determine what rx processing steps are done within the host.
* Possibilities:
* 1. Nothing - rx->tx forwarding and rx PN entirely within target.
* (This is unlikely; even if the target is doing rx->tx forwarding,
* the host should be doing rx->tx forwarding too, as a back up for
* the target's rx->tx forwarding, in case the target runs short on
* memory, and can't store rx->tx frames that are waiting for
* missing prior rx frames to arrive.)
* 2. Just rx -> tx forwarding.
* This is the typical configuration for HL, and a likely
* configuration for LL STA or small APs (e.g. retail APs).
* 3. Both PN check and rx -> tx forwarding.
* This is the typical configuration for large LL APs.
* Host-side PN check without rx->tx forwarding is not a valid
* configuration, since the PN check needs to be done prior to
* the rx->tx forwarding.
*/
if (ol_cfg_is_full_reorder_offload(pdev->ctrl_pdev)) {
/*
* PN check, rx-tx forwarding and rx reorder is done by
* the target
*/
if (ol_cfg_rx_fwd_disabled(pdev->ctrl_pdev))
pdev->rx_opt_proc = ol_rx_in_order_deliver;
else
pdev->rx_opt_proc = ol_rx_fwd_check;
} else {
if (ol_cfg_rx_pn_check(pdev->ctrl_pdev)) {
if (ol_cfg_rx_fwd_disabled(pdev->ctrl_pdev)) {
/*
* PN check done on host,
* rx->tx forwarding not done at all.
*/
pdev->rx_opt_proc = ol_rx_pn_check_only;
} else if (ol_cfg_rx_fwd_check(pdev->ctrl_pdev)) {
/*
* Both PN check and rx->tx forwarding done
* on host.
*/
pdev->rx_opt_proc = ol_rx_pn_check;
} else {
#define TRACESTR01 "invalid config: if rx PN check is on the host,"\
"rx->tx forwarding check needs to also be on the host"
QDF_TRACE(QDF_MODULE_ID_TXRX,
QDF_TRACE_LEVEL_ERROR,
"%s: %s", __func__, TRACESTR01);
#undef TRACESTR01
ret = -EINVAL;
goto control_init_fail;
}
} else {
/* PN check done on target */
if ((!ol_cfg_rx_fwd_disabled(pdev->ctrl_pdev)) &&
ol_cfg_rx_fwd_check(pdev->ctrl_pdev)) {
/*
* rx->tx forwarding done on host (possibly as
* back-up for target-side primary rx->tx
* forwarding)
*/
pdev->rx_opt_proc = ol_rx_fwd_check;
} else {
/*
* rx->tx forwarding either done in target,
* or not done at all
*/
pdev->rx_opt_proc = ol_rx_deliver;
}
}
}
/* initialize mutexes for tx desc alloc and peer lookup */
qdf_spinlock_create(&pdev->peer_ref_mutex);
qdf_spinlock_create(&pdev->rx.mutex);
qdf_spinlock_create(&pdev->last_real_peer_mutex);
qdf_spinlock_create(&pdev->peer_map_unmap_lock);
OL_TXRX_PEER_STATS_MUTEX_INIT(pdev);
if (OL_RX_REORDER_TRACE_ATTACH(pdev) != A_OK) {
ret = -ENOMEM;
goto reorder_trace_attach_fail;
}
if (OL_RX_PN_TRACE_ATTACH(pdev) != A_OK) {
ret = -ENOMEM;
goto pn_trace_attach_fail;
}
/*
* WDI event attach
*/
wdi_event_attach(pdev);
/*
* Initialize rx PN check characteristics for different security types.
*/
qdf_mem_zero(&pdev->rx_pn[0], sizeof(pdev->rx_pn));
/* TKIP: 48-bit TSC, CCMP: 48-bit PN */
pdev->rx_pn[htt_sec_type_tkip].len =
pdev->rx_pn[htt_sec_type_tkip_nomic].len =
pdev->rx_pn[htt_sec_type_aes_ccmp].len = 48;
pdev->rx_pn[htt_sec_type_tkip].cmp =
pdev->rx_pn[htt_sec_type_tkip_nomic].cmp =
pdev->rx_pn[htt_sec_type_aes_ccmp].cmp = ol_rx_pn_cmp48;
/* WAPI: 128-bit PN */
pdev->rx_pn[htt_sec_type_wapi].len = 128;
pdev->rx_pn[htt_sec_type_wapi].cmp = ol_rx_pn_wapi_cmp;
OL_RX_REORDER_TIMEOUT_INIT(pdev);
ol_txrx_dbg("Created pdev %pK\n", pdev);
pdev->cfg.host_addba = ol_cfg_host_addba(pdev->ctrl_pdev);
#ifdef QCA_SUPPORT_PEER_DATA_RX_RSSI
#define OL_TXRX_RSSI_UPDATE_SHIFT_DEFAULT 3
/* #if 1 -- TODO: clean this up */
#define OL_TXRX_RSSI_NEW_WEIGHT_DEFAULT \
/* avg = 100% * new + 0% * old */ \
(1 << OL_TXRX_RSSI_UPDATE_SHIFT_DEFAULT)
/*
* #else
* #define OL_TXRX_RSSI_NEW_WEIGHT_DEFAULT
* //avg = 25% * new + 25% * old
* (1 << (OL_TXRX_RSSI_UPDATE_SHIFT_DEFAULT-2))
* #endif
*/
pdev->rssi_update_shift = OL_TXRX_RSSI_UPDATE_SHIFT_DEFAULT;
pdev->rssi_new_weight = OL_TXRX_RSSI_NEW_WEIGHT_DEFAULT;
#endif
ol_txrx_local_peer_id_pool_init(pdev);
pdev->cfg.ll_pause_txq_limit =
ol_tx_cfg_max_tx_queue_depth_ll(pdev->ctrl_pdev);
/* TX flow control for peer who is in very bad link status */
ol_tx_badpeer_flow_cl_init(pdev);
#ifdef QCA_COMPUTE_TX_DELAY
qdf_mem_zero(&pdev->tx_delay, sizeof(pdev->tx_delay));
qdf_spinlock_create(&pdev->tx_delay.mutex);
/* initialize compute interval with 5 seconds (ESE default) */
pdev->tx_delay.avg_period_ticks = qdf_system_msecs_to_ticks(5000);
{
uint32_t bin_width_1000ticks;
bin_width_1000ticks =
qdf_system_msecs_to_ticks
(QCA_TX_DELAY_HIST_INTERNAL_BIN_WIDTH_MS
* 1000);
/*
* Compute a factor and shift that together are equal to the
* inverse of the bin_width time, so that rather than dividing
* by the bin width time, approximately the same result can be
* obtained much more efficiently by a multiply + shift.
* multiply_factor >> shift = 1 / bin_width_time, so
* multiply_factor = (1 << shift) / bin_width_time.
*
* Pick the shift semi-arbitrarily.
* If we knew statically what the bin_width would be, we could
* choose a shift that minimizes the error.
* Since the bin_width is determined dynamically, simply use a
* shift that is about half of the uint32_t size. This should
* result in a relatively large multiplier value, which
* minimizes error from rounding the multiplier to an integer.
* The rounding error only becomes significant if the tick units
* are on the order of 1 microsecond. In most systems, it is
* expected that the tick units will be relatively low-res,
* on the order of 1 millisecond. In such systems the rounding
* error is negligible.
* It would be more accurate to dynamically try out different
* shifts and choose the one that results in the smallest
* rounding error, but that extra level of fidelity is
* not needed.
*/
pdev->tx_delay.hist_internal_bin_width_shift = 16;
pdev->tx_delay.hist_internal_bin_width_mult =
((1 << pdev->tx_delay.hist_internal_bin_width_shift) *
1000 + (bin_width_1000ticks >> 1)) /
bin_width_1000ticks;
}
#endif /* QCA_COMPUTE_TX_DELAY */
/* Thermal Mitigation */
ol_tx_throttle_init(pdev);
ol_tso_seg_list_init(pdev, desc_pool_size);
ol_tso_num_seg_list_init(pdev, desc_pool_size);
ol_tx_register_flow_control(pdev);
return 0; /* success */
pn_trace_attach_fail:
OL_RX_REORDER_TRACE_DETACH(pdev);
reorder_trace_attach_fail:
qdf_spinlock_destroy(&pdev->peer_ref_mutex);
qdf_spinlock_destroy(&pdev->rx.mutex);
qdf_spinlock_destroy(&pdev->last_real_peer_mutex);
qdf_spinlock_destroy(&pdev->peer_map_unmap_lock);
OL_TXRX_PEER_STATS_MUTEX_DESTROY(pdev);
control_init_fail:
desc_alloc_fail:
for (i = 0; i < fail_idx; i++)
htt_tx_desc_free(pdev->htt_pdev,
(ol_tx_desc_find(pdev, i))->htt_tx_desc);
qdf_mem_multi_pages_free(pdev->osdev,
&pdev->tx_desc.desc_pages, 0, true);
page_alloc_fail:
if (ol_cfg_ipa_uc_offload_enabled(pdev->ctrl_pdev))
htt_ipa_uc_detach(pdev->htt_pdev);
uc_attach_fail:
htt_detach(pdev->htt_pdev);
htt_attach_fail:
ol_tx_desc_dup_detect_deinit(pdev);
ol_attach_fail:
return ret; /* fail */
}
/**
* ol_txrx_pdev_attach_target() - send target configuration
*
* @pdev - the physical device being initialized
*
* The majority of the data SW setup are done by the pdev_attach
* functions, but this function completes the data SW setup by
* sending datapath configuration messages to the target.
*
* Return: 0 - success 1 - failure
*/
static int ol_txrx_pdev_attach_target(struct cdp_pdev *ppdev)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
return htt_attach_target(pdev->htt_pdev) == QDF_STATUS_SUCCESS ? 0:1;
}
/**
* ol_tx_free_descs_inuse - free tx descriptors which are in use
* @pdev - the physical device for which tx descs need to be freed
*
* Cycle through the list of TX descriptors (for a pdev) which are in use,
* for which TX completion has not been received and free them. Should be
* called only when the interrupts are off and all lower layer RX is stopped.
* Otherwise there may be a race condition with TX completions.
*
* Return: None
*/
static void ol_tx_free_descs_inuse(ol_txrx_pdev_handle pdev)
{
int i;
void *htt_tx_desc;
struct ol_tx_desc_t *tx_desc;
int num_freed_tx_desc = 0;
for (i = 0; i < pdev->tx_desc.pool_size; i++) {
tx_desc = ol_tx_desc_find(pdev, i);
/*
* Confirm that each tx descriptor is "empty", i.e. it has
* no tx frame attached.
* In particular, check that there are no frames that have
* been given to the target to transmit, for which the
* target has never provided a response.
*/
if (qdf_atomic_read(&tx_desc->ref_cnt)) {
ol_txrx_dbg("Warning: freeing tx frame (no compltn)");
ol_tx_desc_frame_free_nonstd(pdev,
tx_desc, 1);
num_freed_tx_desc++;
}
htt_tx_desc = tx_desc->htt_tx_desc;
htt_tx_desc_free(pdev->htt_pdev, htt_tx_desc);
}
if (num_freed_tx_desc)
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO,
"freed %d tx frames for which no resp from target",
num_freed_tx_desc);
}
/**
* ol_txrx_pdev_pre_detach() - detach the data SW state
* @pdev - the data physical device object being removed
* @force - delete the pdev (and its vdevs and peers) even if
* there are outstanding references by the target to the vdevs
* and peers within the pdev
*
* This function is used when the WLAN driver is being removed to
* detach the host data component within the driver.
*
* Return: None
*/
static void ol_txrx_pdev_pre_detach(struct cdp_pdev *ppdev, int force)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
/* preconditions */
TXRX_ASSERT2(pdev);
/* check that the pdev has no vdevs allocated */
TXRX_ASSERT1(TAILQ_EMPTY(&pdev->vdev_list));
#ifdef QCA_SUPPORT_TX_THROTTLE
/* Thermal Mitigation */
qdf_timer_stop(&pdev->tx_throttle.phase_timer);
qdf_timer_free(&pdev->tx_throttle.phase_timer);
#ifdef QCA_LL_LEGACY_TX_FLOW_CONTROL
qdf_timer_stop(&pdev->tx_throttle.tx_timer);
qdf_timer_free(&pdev->tx_throttle.tx_timer);
#endif
#endif
if (force) {
/*
* The assertion above confirms that all vdevs within this pdev
* were detached. However, they may not have actually been
* deleted.
* If the vdev had peers which never received a PEER_UNMAP msg
* from the target, then there are still zombie peer objects,
* and the vdev parents of the zombie peers are also zombies,
* hanging around until their final peer gets deleted.
* Go through the peer hash table and delete any peers left.
* As a side effect, this will complete the deletion of any
* vdevs that are waiting for their peers to finish deletion.
*/
ol_txrx_dbg("Force delete for pdev %pK\n",
pdev);
ol_txrx_peer_find_hash_erase(pdev);
}
/* to get flow pool status before freeing descs */
ol_tx_dump_flow_pool_info((void *)pdev);
ol_tx_free_descs_inuse(pdev);
ol_tx_deregister_flow_control(pdev);
/*
* ol_tso_seg_list_deinit should happen after
* ol_tx_deinit_tx_desc_inuse as it tries to access the tso seg freelist
* which is being de-initilized in ol_tso_seg_list_deinit
*/
ol_tso_seg_list_deinit(pdev);
ol_tso_num_seg_list_deinit(pdev);
/* Stop the communication between HTT and target at first */
htt_detach_target(pdev->htt_pdev);
qdf_mem_multi_pages_free(pdev->osdev,
&pdev->tx_desc.desc_pages, 0, true);
pdev->tx_desc.freelist = NULL;
/* Detach micro controller data path offload resource */
if (ol_cfg_ipa_uc_offload_enabled(pdev->ctrl_pdev))
htt_ipa_uc_detach(pdev->htt_pdev);
htt_detach(pdev->htt_pdev);
ol_tx_desc_dup_detect_deinit(pdev);
qdf_spinlock_destroy(&pdev->peer_ref_mutex);
qdf_spinlock_destroy(&pdev->last_real_peer_mutex);
qdf_spinlock_destroy(&pdev->rx.mutex);
qdf_spinlock_destroy(&pdev->peer_map_unmap_lock);
#ifdef QCA_SUPPORT_TX_THROTTLE
/* Thermal Mitigation */
qdf_spinlock_destroy(&pdev->tx_throttle.mutex);
#endif
/* TX flow control for peer who is in very bad link status */
ol_tx_badpeer_flow_cl_deinit(pdev);
OL_TXRX_PEER_STATS_MUTEX_DESTROY(pdev);
OL_RX_REORDER_TRACE_DETACH(pdev);
OL_RX_PN_TRACE_DETACH(pdev);
/*
* WDI event detach
*/
wdi_event_detach(pdev);
ol_txrx_local_peer_id_cleanup(pdev);
#ifdef QCA_COMPUTE_TX_DELAY
qdf_spinlock_destroy(&pdev->tx_delay.mutex);
#endif
}
/**
* ol_txrx_pdev_detach() - delete the data SW state
* @ppdev - the data physical device object being removed
* @force - delete the pdev (and its vdevs and peers) even if
* there are outstanding references by the target to the vdevs
* and peers within the pdev
*
* This function is used when the WLAN driver is being removed to
* remove the host data component within the driver.
* All virtual devices within the physical device need to be deleted
* (ol_txrx_vdev_detach) before the physical device itself is deleted.
*
* Return: None
*/
static void ol_txrx_pdev_detach(struct cdp_pdev *ppdev, int force)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
struct ol_txrx_stats_req_internal *req, *temp_req;
int i = 0;
if (!soc) {
ol_txrx_err("soc is NULL");
return;
}
/*checking to ensure txrx pdev structure is not NULL */
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
htt_pktlogmod_exit(pdev);
qdf_spin_lock_bh(&pdev->req_list_spinlock);
if (pdev->req_list_depth > 0)
ol_txrx_err(
"Warning: the txrx req list is not empty, depth=%d\n",
pdev->req_list_depth
);
TAILQ_FOREACH_SAFE(req, &pdev->req_list, req_list_elem, temp_req) {
TAILQ_REMOVE(&pdev->req_list, req, req_list_elem);
pdev->req_list_depth--;
ol_txrx_err(
"%d: %pK,verbose(%d), concise(%d), up_m(0x%x), reset_m(0x%x)\n",
i++,
req,
req->base.print.verbose,
req->base.print.concise,
req->base.stats_type_upload_mask,
req->base.stats_type_reset_mask
);
qdf_mem_free(req);
}
qdf_spin_unlock_bh(&pdev->req_list_spinlock);
qdf_spinlock_destroy(&pdev->req_list_spinlock);
qdf_spinlock_destroy(&pdev->tx_mutex);
OL_RX_REORDER_TIMEOUT_CLEANUP(pdev);
if (pdev->cfg.is_high_latency)
ol_tx_sched_detach(pdev);
htt_deregister_rx_pkt_dump_callback(pdev->htt_pdev);
htt_pdev_free(pdev->htt_pdev);
ol_txrx_peer_find_detach(pdev);
ol_txrx_tso_stats_deinit(pdev);
ol_txrx_fw_stats_desc_pool_deinit(pdev);
ol_txrx_pdev_txq_log_destroy(pdev);
ol_txrx_pdev_grp_stat_destroy(pdev);
ol_txrx_debugfs_exit(pdev);
soc->pdev_list[pdev->id] = NULL;
qdf_mem_free(pdev);
}
#if defined(QCA_HL_NETDEV_FLOW_CONTROL)
/**
* ol_txrx_vdev_per_vdev_tx_desc_init() - initialise per vdev tx desc count
* related variables.
* @vdev: the virtual device object
*
* Return: None
*/
static inline void
ol_txrx_vdev_per_vdev_tx_desc_init(struct ol_txrx_vdev_t *vdev)
{
qdf_atomic_init(&vdev->tx_desc_count);
vdev->tx_desc_limit = 0;
vdev->queue_restart_th = 0;
vdev->prio_q_paused = 0;
vdev->queue_stop_th = 0;
}
#else
static inline void
ol_txrx_vdev_per_vdev_tx_desc_init(struct ol_txrx_vdev_t *vdev)
{
}
#endif /* QCA_HL_NETDEV_FLOW_CONTROL */
/**
* ol_txrx_vdev_attach - Allocate and initialize the data object
* for a new virtual device.
*
* @data_pdev - the physical device the virtual device belongs to
* @vdev_mac_addr - the MAC address of the virtual device
* @vdev_id - the ID used to identify the virtual device to the target
* @op_mode - whether this virtual device is operating as an AP,
* an IBSS, or a STA
* @subtype: Subtype of the operating vdev
*
* Return: success: handle to new data vdev object, failure: NULL
*/
static struct cdp_vdev *
ol_txrx_vdev_attach(struct cdp_pdev *ppdev,
uint8_t *vdev_mac_addr,
uint8_t vdev_id, enum wlan_op_mode op_mode,
enum wlan_op_subtype subtype)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
struct ol_txrx_vdev_t *vdev;
QDF_STATUS qdf_status;
/* preconditions */
TXRX_ASSERT2(pdev);
TXRX_ASSERT2(vdev_mac_addr);
vdev = qdf_mem_malloc(sizeof(*vdev));
if (!vdev)
return NULL; /* failure */
/* store provided params */
vdev->pdev = pdev;
vdev->vdev_id = vdev_id;
vdev->opmode = op_mode;
vdev->subtype = subtype;
vdev->delete.pending = 0;
vdev->safemode = 0;
vdev->drop_unenc = 1;
vdev->num_filters = 0;
vdev->fwd_tx_packets = 0;
vdev->fwd_rx_packets = 0;
ol_txrx_vdev_per_vdev_tx_desc_init(vdev);
qdf_mem_copy(&vdev->mac_addr.raw[0], vdev_mac_addr,
QDF_MAC_ADDR_SIZE);
TAILQ_INIT(&vdev->peer_list);
vdev->last_real_peer = NULL;
ol_txrx_hl_tdls_flag_reset((struct cdp_vdev *)vdev, false);
#ifdef QCA_IBSS_SUPPORT
vdev->ibss_peer_num = 0;
vdev->ibss_peer_heart_beat_timer = 0;
#endif
ol_txrx_vdev_txqs_init(vdev);
qdf_spinlock_create(&vdev->ll_pause.mutex);
vdev->ll_pause.paused_reason = 0;
vdev->ll_pause.txq.head = vdev->ll_pause.txq.tail = NULL;
vdev->ll_pause.txq.depth = 0;
qdf_atomic_init(&vdev->delete.detaching);
qdf_timer_init(pdev->osdev,
&vdev->ll_pause.timer,
ol_tx_vdev_ll_pause_queue_send, vdev,
QDF_TIMER_TYPE_SW);
qdf_atomic_init(&vdev->os_q_paused);
qdf_atomic_set(&vdev->os_q_paused, 0);
vdev->tx_fl_lwm = 0;
vdev->tx_fl_hwm = 0;
vdev->rx = NULL;
vdev->wait_on_peer_id = OL_TXRX_INVALID_LOCAL_PEER_ID;
qdf_mem_zero(&vdev->last_peer_mac_addr,
sizeof(union ol_txrx_align_mac_addr_t));
qdf_spinlock_create(&vdev->flow_control_lock);
vdev->osif_flow_control_cb = NULL;
vdev->osif_flow_control_is_pause = NULL;
vdev->osif_fc_ctx = NULL;
vdev->txrx_stats.txack_success = 0;
vdev->txrx_stats.txack_failed = 0;
/* Default MAX Q depth for every VDEV */
vdev->ll_pause.max_q_depth =
ol_tx_cfg_max_tx_queue_depth_ll(vdev->pdev->ctrl_pdev);
qdf_status = qdf_event_create(&vdev->wait_delete_comp);
ol_txrx_vdev_init_tcp_del_ack(vdev);
/* add this vdev into the pdev's list */
TAILQ_INSERT_TAIL(&pdev->vdev_list, vdev, vdev_list_elem);
if (QDF_GLOBAL_MONITOR_MODE == cds_get_conparam())
pdev->monitor_vdev = vdev;
ol_txrx_dbg(
"Created vdev %pK ("QDF_MAC_ADDR_STR")\n",
vdev,
QDF_MAC_ADDR_ARRAY(vdev->mac_addr.raw));
/*
* We've verified that htt_op_mode == wlan_op_mode,
* so no translation is needed.
*/
htt_vdev_attach(pdev->htt_pdev, vdev_id, op_mode);
return (struct cdp_vdev *)vdev;
}
/**
*ol_txrx_vdev_register - Link a vdev's data object with the
* matching OS shim vdev object.
*
* @txrx_vdev: the virtual device's data object
* @osif_vdev: the virtual device's OS shim object
* @ctrl_vdev: UMAC vdev objmgr handle
* @txrx_ops: (pointers to)functions used for tx and rx data xfer
*
* The data object for a virtual device is created by the
* function ol_txrx_vdev_attach. However, rather than fully
* linking the data vdev object with the vdev objects from the
* other subsystems that the data vdev object interacts with,
* the txrx_vdev_attach function focuses primarily on creating
* the data vdev object. After the creation of both the data
* vdev object and the OS shim vdev object, this
* txrx_osif_vdev_attach function is used to connect the two
* vdev objects, so the data SW can use the OS shim vdev handle
* when passing rx data received by a vdev up to the OS shim.
*/
static void ol_txrx_vdev_register(struct cdp_vdev *pvdev, void *osif_vdev,
struct cdp_ctrl_objmgr_vdev *ctrl_vdev,
struct ol_txrx_ops *txrx_ops)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
if (qdf_unlikely(!vdev) || qdf_unlikely(!txrx_ops)) {
qdf_print("vdev/txrx_ops is NULL!");
qdf_assert(0);
return;
}
vdev->osif_dev = osif_vdev;
vdev->ctrl_vdev = ctrl_vdev;
vdev->rx = txrx_ops->rx.rx;
vdev->stats_rx = txrx_ops->rx.stats_rx;
vdev->tx_comp = txrx_ops->tx.tx_comp;
txrx_ops->tx.tx = ol_tx_data;
}
void ol_txrx_set_safemode(ol_txrx_vdev_handle vdev, uint32_t val)
{
vdev->safemode = val;
}
/**
* ol_txrx_set_privacy_filters - set the privacy filter
* @vdev - the data virtual device object
* @filter - filters to be set
* @num - the number of filters
*
* Rx related. Set the privacy filters. When rx packets, check
* the ether type, filter type and packet type to decide whether
* discard these packets.
*/
static void
ol_txrx_set_privacy_filters(ol_txrx_vdev_handle vdev,
void *filters, uint32_t num)
{
qdf_mem_copy(vdev->privacy_filters, filters,
num * sizeof(struct privacy_exemption));
vdev->num_filters = num;
}
void ol_txrx_set_drop_unenc(ol_txrx_vdev_handle vdev, uint32_t val)
{
vdev->drop_unenc = val;
}
#if defined(CONFIG_HL_SUPPORT) || defined(QCA_LL_LEGACY_TX_FLOW_CONTROL)
static void
ol_txrx_tx_desc_reset_vdev(ol_txrx_vdev_handle vdev)
{
struct ol_txrx_pdev_t *pdev = vdev->pdev;
int i;
struct ol_tx_desc_t *tx_desc;
qdf_spin_lock_bh(&pdev->tx_mutex);
for (i = 0; i < pdev->tx_desc.pool_size; i++) {
tx_desc = ol_tx_desc_find(pdev, i);
if (tx_desc->vdev == vdev)
tx_desc->vdev = NULL;
}
qdf_spin_unlock_bh(&pdev->tx_mutex);
}
#else
#ifdef QCA_LL_TX_FLOW_CONTROL_V2
static void ol_txrx_tx_desc_reset_vdev(ol_txrx_vdev_handle vdev)
{
struct ol_txrx_pdev_t *pdev = vdev->pdev;
struct ol_tx_flow_pool_t *pool;
int i;
struct ol_tx_desc_t *tx_desc;
qdf_spin_lock_bh(&pdev->tx_desc.flow_pool_list_lock);
for (i = 0; i < pdev->tx_desc.pool_size; i++) {
tx_desc = ol_tx_desc_find(pdev, i);
if (!qdf_atomic_read(&tx_desc->ref_cnt))
/* not in use */
continue;
pool = tx_desc->pool;
qdf_spin_lock_bh(&pool->flow_pool_lock);
if (tx_desc->vdev == vdev)
tx_desc->vdev = NULL;
qdf_spin_unlock_bh(&pool->flow_pool_lock);
}
qdf_spin_unlock_bh(&pdev->tx_desc.flow_pool_list_lock);
}
#else
static void
ol_txrx_tx_desc_reset_vdev(ol_txrx_vdev_handle vdev)
{
}
#endif /* QCA_LL_TX_FLOW_CONTROL_V2 */
#endif /* CONFIG_HL_SUPPORT */
/**
* ol_txrx_vdev_detach - Deallocate the specified data virtual
* device object.
* @data_vdev: data object for the virtual device in question
* @callback: function to call (if non-NULL) once the vdev has
* been wholly deleted
* @callback_context: context to provide in the callback
*
* All peers associated with the virtual device need to be deleted
* (ol_txrx_peer_detach) before the virtual device itself is deleted.
* However, for the peers to be fully deleted, the peer deletion has to
* percolate through the target data FW and back up to the host data SW.
* Thus, even though the host control SW may have issued a peer_detach
* call for each of the vdev's peers, the peer objects may still be
* allocated, pending removal of all references to them by the target FW.
* In this case, though the vdev_detach function call will still return
* immediately, the vdev itself won't actually be deleted, until the
* deletions of all its peers complete.
* The caller can provide a callback function pointer to be notified when
* the vdev deletion actually happens - whether it's directly within the
* vdev_detach call, or if it's deferred until all in-progress peer
* deletions have completed.
*/
static void
ol_txrx_vdev_detach(struct cdp_vdev *pvdev,
ol_txrx_vdev_delete_cb callback, void *context)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
struct ol_txrx_pdev_t *pdev;
/* preconditions */
TXRX_ASSERT2(vdev);
pdev = vdev->pdev;
/* prevent anyone from restarting the ll_pause timer again */
qdf_atomic_set(&vdev->delete.detaching, 1);
ol_txrx_vdev_tx_queue_free(vdev);
qdf_spin_lock_bh(&vdev->ll_pause.mutex);
qdf_timer_stop(&vdev->ll_pause.timer);
vdev->ll_pause.is_q_timer_on = false;
while (vdev->ll_pause.txq.head) {
qdf_nbuf_t next = qdf_nbuf_next(vdev->ll_pause.txq.head);
qdf_nbuf_set_next(vdev->ll_pause.txq.head, NULL);
qdf_nbuf_tx_free(vdev->ll_pause.txq.head, QDF_NBUF_PKT_ERROR);
vdev->ll_pause.txq.head = next;
}
qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
/* ll_pause timer should be deleted without any locks held, and
* no timer function should be executed after this point because
* qdf_timer_free is deleting the timer synchronously.
*/
qdf_timer_free(&vdev->ll_pause.timer);
qdf_spinlock_destroy(&vdev->ll_pause.mutex);
qdf_spin_lock_bh(&vdev->flow_control_lock);
vdev->osif_flow_control_cb = NULL;
vdev->osif_flow_control_is_pause = NULL;
vdev->osif_fc_ctx = NULL;
qdf_spin_unlock_bh(&vdev->flow_control_lock);
qdf_spinlock_destroy(&vdev->flow_control_lock);
/* remove the vdev from its parent pdev's list */
TAILQ_REMOVE(&pdev->vdev_list, vdev, vdev_list_elem);
/*
* Use peer_ref_mutex while accessing peer_list, in case
* a peer is in the process of being removed from the list.
*/
qdf_spin_lock_bh(&pdev->peer_ref_mutex);
/* check that the vdev has no peers allocated */
if (!TAILQ_EMPTY(&vdev->peer_list)) {
/* debug print - will be removed later */
ol_txrx_dbg(
"not deleting vdev object %pK ("QDF_MAC_ADDR_STR") until deletion finishes for all its peers\n",
vdev,
QDF_MAC_ADDR_ARRAY(vdev->mac_addr.raw));
/* indicate that the vdev needs to be deleted */
vdev->delete.pending = 1;
vdev->delete.callback = callback;
vdev->delete.context = context;
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
return;
}
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
qdf_event_destroy(&vdev->wait_delete_comp);
ol_txrx_dbg(
"deleting vdev obj %pK ("QDF_MAC_ADDR_STR")\n",
vdev,
QDF_MAC_ADDR_ARRAY(vdev->mac_addr.raw));
htt_vdev_detach(pdev->htt_pdev, vdev->vdev_id);
/*
* The ol_tx_desc_free might access the invalid content of vdev referred
* by tx desc, since this vdev might be detached in another thread
* asynchronous.
*
* Go through tx desc pool to set corresponding tx desc's vdev to NULL
* when detach this vdev, and add vdev checking in the ol_tx_desc_free
* to avoid crash.
*
*/
ol_txrx_tx_desc_reset_vdev(vdev);
/*
* Doesn't matter if there are outstanding tx frames -
* they will be freed once the target sends a tx completion
* message for them.
*/
qdf_mem_free(vdev);
if (callback)
callback(context);
}
/**
* ol_txrx_flush_rx_frames() - flush cached rx frames
* @peer: peer
* @drop: set flag to drop frames
*
* Return: None
*/
void ol_txrx_flush_rx_frames(struct ol_txrx_peer_t *peer,
bool drop)
{
struct ol_txrx_cached_bufq_t *bufqi;
struct ol_rx_cached_buf *cache_buf;
QDF_STATUS ret;
ol_txrx_rx_fp data_rx = NULL;
if (qdf_atomic_inc_return(&peer->flush_in_progress) > 1) {
qdf_atomic_dec(&peer->flush_in_progress);
return;
}
qdf_assert(peer->vdev);
qdf_spin_lock_bh(&peer->peer_info_lock);
bufqi = &peer->bufq_info;
if (peer->state >= OL_TXRX_PEER_STATE_CONN && peer->vdev->rx)
data_rx = peer->vdev->rx;
else
drop = true;
qdf_spin_unlock_bh(&peer->peer_info_lock);
qdf_spin_lock_bh(&bufqi->bufq_lock);
cache_buf = list_entry((&bufqi->cached_bufq)->next,
typeof(*cache_buf), list);
while (!list_empty(&bufqi->cached_bufq)) {
list_del(&cache_buf->list);
bufqi->curr--;
qdf_assert(bufqi->curr >= 0);
qdf_spin_unlock_bh(&bufqi->bufq_lock);
if (drop) {
qdf_nbuf_free(cache_buf->buf);
} else {
/* Flush the cached frames to HDD */
ret = data_rx(peer->vdev->osif_dev, cache_buf->buf);
if (ret != QDF_STATUS_SUCCESS)
qdf_nbuf_free(cache_buf->buf);
}
qdf_mem_free(cache_buf);
qdf_spin_lock_bh(&bufqi->bufq_lock);
cache_buf = list_entry((&bufqi->cached_bufq)->next,
typeof(*cache_buf), list);
}
bufqi->qdepth_no_thresh = bufqi->curr;
qdf_spin_unlock_bh(&bufqi->bufq_lock);
qdf_atomic_dec(&peer->flush_in_progress);
}
static void ol_txrx_flush_cache_rx_queue(void)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
struct ol_txrx_peer_t *peer;
struct ol_txrx_vdev_t *vdev;
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev)
return;
TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) {
TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) {
ol_txrx_flush_rx_frames(peer, 1);
}
}
}
/* Define short name to use in cds_trigger_recovery */
#define PEER_DEL_TIMEOUT QDF_PEER_DELETION_TIMEDOUT
/**
* ol_txrx_dump_peer_access_list() - dump peer access list
* @peer: peer handle
*
* This function will dump if any peer debug ids are still accessing peer
*
* Return: None
*/
static void ol_txrx_dump_peer_access_list(ol_txrx_peer_handle peer)
{
u32 i;
u32 pending_ref;
for (i = 0; i < PEER_DEBUG_ID_MAX; i++) {
pending_ref = qdf_atomic_read(&peer->access_list[i]);
if (pending_ref)
ol_txrx_info_high("id %d pending refs %d",
i, pending_ref);
}
}
/**
* ol_txrx_peer_attach - Allocate and set up references for a
* data peer object.
* @data_pdev: data physical device object that will indirectly
* own the data_peer object
* @data_vdev - data virtual device object that will directly
* own the data_peer object
* @peer_mac_addr - MAC address of the new peer
*
* When an association with a peer starts, the host's control SW
* uses this function to inform the host data SW.
* The host data SW allocates its own peer object, and stores a
* reference to the control peer object within the data peer object.
* The host data SW also stores a reference to the virtual device
* that the peer is associated with. This virtual device handle is
* used when the data SW delivers rx data frames to the OS shim layer.
* The host data SW returns a handle to the new peer data object,
* so a reference within the control peer object can be set to the
* data peer object.
*
* Return: handle to new data peer object, or NULL if the attach
* fails
*/
static void *
ol_txrx_peer_attach(struct cdp_vdev *pvdev, uint8_t *peer_mac_addr,
struct cdp_ctrl_objmgr_peer *ctrl_peer)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
struct ol_txrx_peer_t *peer;
struct ol_txrx_peer_t *temp_peer;
uint8_t i;
bool wait_on_deletion = false;
unsigned long rc;
struct ol_txrx_pdev_t *pdev;
bool cmp_wait_mac = false;
uint8_t zero_mac_addr[QDF_MAC_ADDR_SIZE] = { 0, 0, 0, 0, 0, 0 };
u8 check_valid = 0;
/* preconditions */
TXRX_ASSERT2(vdev);
TXRX_ASSERT2(peer_mac_addr);
pdev = vdev->pdev;
TXRX_ASSERT2(pdev);
if (pdev->enable_peer_unmap_conf_support)
check_valid = 1;
if (qdf_mem_cmp(&zero_mac_addr, &vdev->last_peer_mac_addr,
QDF_MAC_ADDR_SIZE))
cmp_wait_mac = true;
qdf_spin_lock_bh(&pdev->peer_ref_mutex);
/* check for duplicate existing peer */
TAILQ_FOREACH(temp_peer, &vdev->peer_list, peer_list_elem) {
if (!ol_txrx_peer_find_mac_addr_cmp(&temp_peer->mac_addr,
(union ol_txrx_align_mac_addr_t *)peer_mac_addr) &&
(check_valid == 0 || temp_peer->valid)) {
ol_txrx_info_high(
"vdev_id %d ("QDF_MAC_ADDR_STR") already exists.\n",
vdev->vdev_id,
QDF_MAC_ADDR_ARRAY(peer_mac_addr));
if (qdf_atomic_read(&temp_peer->delete_in_progress)) {
vdev->wait_on_peer_id = temp_peer->local_id;
qdf_event_reset(&vdev->wait_delete_comp);
wait_on_deletion = true;
break;
} else {
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
return NULL;
}
}
if (cmp_wait_mac && !ol_txrx_peer_find_mac_addr_cmp(
&temp_peer->mac_addr,
&vdev->last_peer_mac_addr) &&
(check_valid == 0 ||
temp_peer->valid)) {
ol_txrx_info_high(
"vdev_id %d ("QDF_MAC_ADDR_STR") old peer exists.\n",
vdev->vdev_id,
QDF_MAC_ADDR_ARRAY(vdev->last_peer_mac_addr.raw));
if (qdf_atomic_read(&temp_peer->delete_in_progress)) {
vdev->wait_on_peer_id = temp_peer->local_id;
qdf_event_reset(&vdev->wait_delete_comp);
wait_on_deletion = true;
break;
} else {
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
ol_txrx_err("peer not found");
return NULL;
}
}
}
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
qdf_mem_zero(&vdev->last_peer_mac_addr,
sizeof(union ol_txrx_align_mac_addr_t));
if (wait_on_deletion) {
/* wait for peer deletion */
rc = qdf_wait_for_event_completion(&vdev->wait_delete_comp,
PEER_DELETION_TIMEOUT);
if (QDF_STATUS_SUCCESS != rc) {
ol_txrx_err("error waiting for peer_id(%d) deletion, status %d\n",
vdev->wait_on_peer_id, (int) rc);
/* Added for debugging only */
ol_txrx_dump_peer_access_list(temp_peer);
wlan_roam_debug_dump_table();
vdev->wait_on_peer_id = OL_TXRX_INVALID_LOCAL_PEER_ID;
return NULL;
}
}
peer = qdf_mem_malloc(sizeof(*peer));
if (!peer)
return NULL; /* failure */
/* store provided params */
peer->vdev = vdev;
peer->ctrl_peer = peer->ctrl_peer;
qdf_mem_copy(&peer->mac_addr.raw[0], peer_mac_addr,
QDF_MAC_ADDR_SIZE);
ol_txrx_peer_txqs_init(pdev, peer);
INIT_LIST_HEAD(&peer->bufq_info.cached_bufq);
qdf_spin_lock_bh(&pdev->peer_ref_mutex);
/* add this peer into the vdev's list */
TAILQ_INSERT_TAIL(&vdev->peer_list, peer, peer_list_elem);
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
/* check whether this is a real peer (peer mac addr != vdev mac addr) */
if (ol_txrx_peer_find_mac_addr_cmp(&vdev->mac_addr, &peer->mac_addr)) {
qdf_spin_lock_bh(&pdev->last_real_peer_mutex);
vdev->last_real_peer = peer;
qdf_spin_unlock_bh(&pdev->last_real_peer_mutex);
}
peer->rx_opt_proc = pdev->rx_opt_proc;
ol_rx_peer_init(pdev, peer);
/* initialize the peer_id */
for (i = 0; i < MAX_NUM_PEER_ID_PER_PEER; i++)
peer->peer_ids[i] = HTT_INVALID_PEER;
qdf_spinlock_create(&peer->peer_info_lock);
qdf_spinlock_create(&peer->bufq_info.bufq_lock);
peer->bufq_info.thresh = OL_TXRX_CACHED_BUFQ_THRESH;
qdf_atomic_init(&peer->delete_in_progress);
qdf_atomic_init(&peer->flush_in_progress);
qdf_atomic_init(&peer->ref_cnt);
for (i = 0; i < PEER_DEBUG_ID_MAX; i++)
qdf_atomic_init(&peer->access_list[i]);
/* keep one reference for attach */
ol_txrx_peer_get_ref(peer, PEER_DEBUG_ID_OL_PEER_ATTACH);
/* Set a flag to indicate peer create is pending in firmware */
qdf_atomic_init(&peer->fw_create_pending);
qdf_atomic_set(&peer->fw_create_pending, 1);
peer->valid = 1;
qdf_timer_init(pdev->osdev, &peer->peer_unmap_timer,
peer_unmap_timer_handler, peer, QDF_TIMER_TYPE_SW);
ol_txrx_peer_find_hash_add(pdev, peer);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_HIGH,
"vdev %pK created peer %pK ref_cnt %d ("QDF_MAC_ADDR_STR")\n",
vdev, peer, qdf_atomic_read(&peer->ref_cnt),
QDF_MAC_ADDR_ARRAY(peer->mac_addr.raw));
/*
* For every peer MAp message search and set if bss_peer
*/
if (qdf_mem_cmp(peer->mac_addr.raw, vdev->mac_addr.raw,
QDF_MAC_ADDR_SIZE))
peer->bss_peer = 1;
/*
* The peer starts in the "disc" state while association is in progress.
* Once association completes, the peer will get updated to "auth" state
* by a call to ol_txrx_peer_state_update if the peer is in open mode,
* or else to the "conn" state. For non-open mode, the peer will
* progress to "auth" state once the authentication completes.
*/
peer->state = OL_TXRX_PEER_STATE_INVALID;
ol_txrx_peer_state_update((struct cdp_pdev *)pdev, peer->mac_addr.raw,
OL_TXRX_PEER_STATE_DISC);
#ifdef QCA_SUPPORT_PEER_DATA_RX_RSSI
peer->rssi_dbm = HTT_RSSI_INVALID;
#endif
if ((QDF_GLOBAL_MONITOR_MODE == cds_get_conparam()) &&
!pdev->self_peer) {
pdev->self_peer = peer;
/*
* No Tx in monitor mode, otherwise results in target assert.
* Setting disable_intrabss_fwd to true
*/
ol_vdev_rx_set_intrabss_fwd((struct cdp_vdev *)vdev, true);
}
ol_txrx_local_peer_id_alloc(pdev, peer);
return (void *)peer;
}
#undef PEER_DEL_TIMEOUT
/*
* Discarding tx filter - removes all data frames (disconnected state)
*/
static A_STATUS ol_tx_filter_discard(struct ol_txrx_msdu_info_t *tx_msdu_info)
{
return A_ERROR;
}
/*
* Non-autentication tx filter - filters out data frames that are not
* related to authentication, but allows EAPOL (PAE) or WAPI (WAI)
* data frames (connected state)
*/
static A_STATUS ol_tx_filter_non_auth(struct ol_txrx_msdu_info_t *tx_msdu_info)
{
return
(tx_msdu_info->htt.info.ethertype == ETHERTYPE_PAE ||
tx_msdu_info->htt.info.ethertype ==
ETHERTYPE_WAI) ? A_OK : A_ERROR;
}
/*
* Pass-through tx filter - lets all data frames through (authenticated state)
*/
static A_STATUS ol_tx_filter_pass_thru(struct ol_txrx_msdu_info_t *tx_msdu_info)
{
return A_OK;
}
/**
* ol_txrx_peer_get_peer_mac_addr() - return mac_addr from peer handle.
* @peer: handle to peer
*
* returns mac addrs for module which do not know peer type
*
* Return: the mac_addr from peer
*/
static uint8_t *
ol_txrx_peer_get_peer_mac_addr(void *ppeer)
{
ol_txrx_peer_handle peer = ppeer;
if (!peer)
return NULL;
return peer->mac_addr.raw;
}
#ifdef WLAN_FEATURE_11W
/**
* ol_txrx_get_pn_info() - Returns pn info from peer
* @peer: handle to peer
* @last_pn_valid: return last_rmf_pn_valid value from peer.
* @last_pn: return last_rmf_pn value from peer.
* @rmf_pn_replays: return rmf_pn_replays value from peer.
*
* Return: NONE
*/
void
ol_txrx_get_pn_info(void *ppeer, uint8_t **last_pn_valid,
uint64_t **last_pn, uint32_t **rmf_pn_replays)
{
ol_txrx_peer_handle peer = ppeer;
*last_pn_valid = &peer->last_rmf_pn_valid;
*last_pn = &peer->last_rmf_pn;
*rmf_pn_replays = &peer->rmf_pn_replays;
}
#else
void
ol_txrx_get_pn_info(void *ppeer, uint8_t **last_pn_valid,
uint64_t **last_pn, uint32_t **rmf_pn_replays)
{
}
#endif
/**
* ol_txrx_get_opmode() - Return operation mode of vdev
* @vdev: vdev handle
*
* Return: operation mode.
*/
static int ol_txrx_get_opmode(struct cdp_vdev *pvdev)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
return vdev->opmode;
}
/**
* ol_txrx_get_peer_state() - Return peer state of peer
* @peer: peer handle
*
* Return: return peer state
*/
static int ol_txrx_get_peer_state(void *ppeer)
{
ol_txrx_peer_handle peer = ppeer;
return peer->state;
}
/**
* ol_txrx_get_vdev_for_peer() - Return vdev from peer handle
* @peer: peer handle
*
* Return: vdev handle from peer
*/
static struct cdp_vdev *ol_txrx_get_vdev_for_peer(void *ppeer)
{
ol_txrx_peer_handle peer = ppeer;
return (struct cdp_vdev *)peer->vdev;
}
/**
* ol_txrx_get_vdev_mac_addr() - Return mac addr of vdev
* @vdev: vdev handle
*
* Return: vdev mac address
*/
static uint8_t *
ol_txrx_get_vdev_mac_addr(struct cdp_vdev *pvdev)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
if (!vdev)
return NULL;
return vdev->mac_addr.raw;
}
#ifdef currently_unused
/**
* ol_txrx_get_vdev_struct_mac_addr() - Return handle to struct qdf_mac_addr of
* vdev
* @vdev: vdev handle
*
* Return: Handle to struct qdf_mac_addr
*/
struct qdf_mac_addr *
ol_txrx_get_vdev_struct_mac_addr(ol_txrx_vdev_handle vdev)
{
return (struct qdf_mac_addr *)&(vdev->mac_addr);
}
#endif
#ifdef currently_unused
/**
* ol_txrx_get_pdev_from_vdev() - Return handle to pdev of vdev
* @vdev: vdev handle
*
* Return: Handle to pdev
*/
ol_txrx_pdev_handle ol_txrx_get_pdev_from_vdev(ol_txrx_vdev_handle vdev)
{
return vdev->pdev;
}
#endif
/**
* ol_txrx_get_ctrl_pdev_from_vdev() - Return control pdev of vdev
* @vdev: vdev handle
*
* Return: Handle to control pdev
*/
static struct cdp_cfg *
ol_txrx_get_ctrl_pdev_from_vdev(struct cdp_vdev *pvdev)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
return vdev->pdev->ctrl_pdev;
}
/**
* ol_txrx_is_rx_fwd_disabled() - returns the rx_fwd_disabled status on vdev
* @vdev: vdev handle
*
* Return: Rx Fwd disabled status
*/
static uint8_t
ol_txrx_is_rx_fwd_disabled(struct cdp_vdev *pvdev)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
struct txrx_pdev_cfg_t *cfg = (struct txrx_pdev_cfg_t *)
vdev->pdev->ctrl_pdev;
return cfg->rx_fwd_disabled;
}
#ifdef QCA_IBSS_SUPPORT
/**
* ol_txrx_update_ibss_add_peer_num_of_vdev() - update and return peer num
* @vdev: vdev handle
* @peer_num_delta: peer nums to be adjusted
*
* Return: -1 for failure or total peer nums after adjustment.
*/
static int16_t
ol_txrx_update_ibss_add_peer_num_of_vdev(struct cdp_vdev *pvdev,
int16_t peer_num_delta)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
int16_t new_peer_num;
new_peer_num = vdev->ibss_peer_num + peer_num_delta;
if (new_peer_num > MAX_PEERS || new_peer_num < 0)
return OL_TXRX_INVALID_NUM_PEERS;
vdev->ibss_peer_num = new_peer_num;
return new_peer_num;
}
/**
* ol_txrx_set_ibss_vdev_heart_beat_timer() - Update ibss vdev heart
* beat timer
* @vdev: vdev handle
* @timer_value_sec: new heart beat timer value
*
* Return: Old timer value set in vdev.
*/
static uint16_t ol_txrx_set_ibss_vdev_heart_beat_timer(struct cdp_vdev *pvdev,
uint16_t timer_value_sec)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
uint16_t old_timer_value = vdev->ibss_peer_heart_beat_timer;
vdev->ibss_peer_heart_beat_timer = timer_value_sec;
return old_timer_value;
}
#else /* !QCA_IBSS_SUPPORT */
static inline int16_t
ol_txrx_update_ibss_add_peer_num_of_vdev(struct cdp_vdev *pvdev,
int16_t peer_num_delta)
{
return 0;
}
static inline uint16_t
ol_txrx_set_ibss_vdev_heart_beat_timer(struct cdp_vdev *pvdev,
uint16_t timer_value_sec)
{
return 0;
}
#endif /* QCA_IBSS_SUPPORT */
/**
* ol_txrx_remove_peers_for_vdev() - remove all vdev peers with lock held
* @vdev: vdev handle
* @callback: callback function to remove the peer.
* @callback_context: handle for callback function
* @remove_last_peer: Does it required to last peer.
*
* Return: NONE
*/
static void
ol_txrx_remove_peers_for_vdev(struct cdp_vdev *pvdev,
ol_txrx_vdev_peer_remove_cb callback,
void *callback_context, bool remove_last_peer)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
ol_txrx_peer_handle peer, temp;
int self_removed = 0;
/* remove all remote peers for vdev */
qdf_spin_lock_bh(&vdev->pdev->peer_ref_mutex);
temp = NULL;
TAILQ_FOREACH_REVERSE(peer, &vdev->peer_list, peer_list_t,
peer_list_elem) {
if (qdf_atomic_read(&peer->delete_in_progress))
continue;
if (temp) {
qdf_spin_unlock_bh(&vdev->pdev->peer_ref_mutex);
callback(callback_context, temp->mac_addr.raw,
vdev->vdev_id, temp);
qdf_spin_lock_bh(&vdev->pdev->peer_ref_mutex);
}
/* self peer is deleted last */
if (peer == TAILQ_FIRST(&vdev->peer_list)) {
self_removed = 1;
break;
}
temp = peer;
}
qdf_spin_unlock_bh(&vdev->pdev->peer_ref_mutex);
if (self_removed)
ol_txrx_info("self peer removed by caller");
if (remove_last_peer) {
/* remove IBSS bss peer last */
peer = TAILQ_FIRST(&vdev->peer_list);
callback(callback_context, (uint8_t *) &vdev->mac_addr,
vdev->vdev_id, peer);
}
}
/**
* ol_txrx_remove_peers_for_vdev_no_lock() - remove vdev peers with no lock.
* @vdev: vdev handle
* @callback: callback function to remove the peer.
* @callback_context: handle for callback function
*
* Return: NONE
*/
static void
ol_txrx_remove_peers_for_vdev_no_lock(struct cdp_vdev *pvdev,
ol_txrx_vdev_peer_remove_cb callback,
void *callback_context)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
ol_txrx_peer_handle peer = NULL;
ol_txrx_peer_handle tmp_peer = NULL;
TAILQ_FOREACH_SAFE(peer, &vdev->peer_list, peer_list_elem, tmp_peer) {
ol_txrx_info_high(
"peer found for vdev id %d. deleting the peer",
vdev->vdev_id);
callback(callback_context, (uint8_t *)&vdev->mac_addr,
vdev->vdev_id, peer);
}
}
#ifdef WLAN_FEATURE_DSRC
/**
* ol_txrx_set_ocb_chan_info() - set OCB channel info to vdev.
* @vdev: vdev handle
* @ocb_set_chan: OCB channel information to be set in vdev.
*
* Return: NONE
*/
static void ol_txrx_set_ocb_chan_info(struct cdp_vdev *pvdev,
struct ol_txrx_ocb_set_chan ocb_set_chan)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
vdev->ocb_channel_info = ocb_set_chan.ocb_channel_info;
vdev->ocb_channel_count = ocb_set_chan.ocb_channel_count;
}
/**
* ol_txrx_get_ocb_chan_info() - return handle to vdev ocb_channel_info
* @vdev: vdev handle
*
* Return: handle to struct ol_txrx_ocb_chan_info
*/
static struct ol_txrx_ocb_chan_info *
ol_txrx_get_ocb_chan_info(struct cdp_vdev *pvdev)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
return vdev->ocb_channel_info;
}
#endif
/**
* @brief specify the peer's authentication state
* @details
* Specify the peer's authentication state (none, connected, authenticated)
* to allow the data SW to determine whether to filter out invalid data frames.
* (In the "connected" state, where security is enabled, but authentication
* has not completed, tx and rx data frames other than EAPOL or WAPI should
* be discarded.)
* This function is only relevant for systems in which the tx and rx filtering
* are done in the host rather than in the target.
*
* @param data_peer - which peer has changed its state
* @param state - the new state of the peer
*
* Return: QDF Status
*/
QDF_STATUS ol_txrx_peer_state_update(struct cdp_pdev *ppdev,
uint8_t *peer_mac,
enum ol_txrx_peer_state state)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
struct ol_txrx_peer_t *peer;
int peer_ref_cnt;
if (qdf_unlikely(!pdev)) {
ol_txrx_err("Pdev is NULL");
qdf_assert(0);
return QDF_STATUS_E_INVAL;
}
peer = ol_txrx_peer_find_hash_find_get_ref(pdev, peer_mac, 0, 1,
PEER_DEBUG_ID_OL_INTERNAL);
if (!peer) {
ol_txrx_err(
"peer is null for peer_mac 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
peer_mac[0], peer_mac[1], peer_mac[2], peer_mac[3],
peer_mac[4], peer_mac[5]);
return QDF_STATUS_E_INVAL;
}
/* TODO: Should we send WMI command of the connection state? */
/* avoid multiple auth state change. */
if (peer->state == state) {
#ifdef TXRX_PRINT_VERBOSE_ENABLE
ol_txrx_dbg("no state change, returns directly");
#endif
peer_ref_cnt = ol_txrx_peer_release_ref
(peer,
PEER_DEBUG_ID_OL_INTERNAL);
return QDF_STATUS_SUCCESS;
}
ol_txrx_dbg("change from %d to %d",
peer->state, state);
peer->tx_filter = (state == OL_TXRX_PEER_STATE_AUTH)
? ol_tx_filter_pass_thru
: ((state == OL_TXRX_PEER_STATE_CONN)
? ol_tx_filter_non_auth
: ol_tx_filter_discard);
if (peer->vdev->pdev->cfg.host_addba) {
if (state == OL_TXRX_PEER_STATE_AUTH) {
int tid;
/*
* Pause all regular (non-extended) TID tx queues until
* data arrives and ADDBA negotiation has completed.
*/
ol_txrx_dbg("pause peer and unpause mgmt/non-qos");
ol_txrx_peer_pause(peer); /* pause all tx queues */
/* unpause mgmt and non-QoS tx queues */
for (tid = OL_TX_NUM_QOS_TIDS;
tid < OL_TX_NUM_TIDS; tid++)
ol_txrx_peer_tid_unpause(peer, tid);
}
}
peer_ref_cnt = ol_txrx_peer_release_ref(peer,
PEER_DEBUG_ID_OL_INTERNAL);
/*
* after ol_txrx_peer_release_ref, peer object cannot be accessed
* if the return code was 0
*/
if (peer_ref_cnt > 0)
/*
* Set the state after the Pause to avoid the race condiction
* with ADDBA check in tx path
*/
peer->state = state;
return QDF_STATUS_SUCCESS;
}
void
ol_txrx_peer_keyinstalled_state_update(struct ol_txrx_peer_t *peer, uint8_t val)
{
peer->keyinstalled = val;
}
void
ol_txrx_peer_update(ol_txrx_vdev_handle vdev,
uint8_t *peer_mac,
union ol_txrx_peer_update_param_t *param,
enum ol_txrx_peer_update_select_t select)
{
struct ol_txrx_peer_t *peer;
peer = ol_txrx_peer_find_hash_find_get_ref(vdev->pdev, peer_mac, 0, 1,
PEER_DEBUG_ID_OL_INTERNAL);
if (!peer) {
ol_txrx_dbg("peer is null");
return;
}
switch (select) {
case ol_txrx_peer_update_qos_capable:
{
/* save qos_capable here txrx peer,
* when HTT_ISOC_T2H_MSG_TYPE_PEER_INFO comes then save.
*/
peer->qos_capable = param->qos_capable;
/*
* The following function call assumes that the peer has a
* single ID. This is currently true, and
* is expected to remain true.
*/
htt_peer_qos_update(peer->vdev->pdev->htt_pdev,
peer->peer_ids[0],
peer->qos_capable);
break;
}
case ol_txrx_peer_update_uapsdMask:
{
peer->uapsd_mask = param->uapsd_mask;
htt_peer_uapsdmask_update(peer->vdev->pdev->htt_pdev,
peer->peer_ids[0],
peer->uapsd_mask);
break;
}
case ol_txrx_peer_update_peer_security:
{
enum ol_sec_type sec_type = param->sec_type;
enum htt_sec_type peer_sec_type = htt_sec_type_none;
switch (sec_type) {
case ol_sec_type_none:
peer_sec_type = htt_sec_type_none;
break;
case ol_sec_type_wep128:
peer_sec_type = htt_sec_type_wep128;
break;
case ol_sec_type_wep104:
peer_sec_type = htt_sec_type_wep104;
break;
case ol_sec_type_wep40:
peer_sec_type = htt_sec_type_wep40;
break;
case ol_sec_type_tkip:
peer_sec_type = htt_sec_type_tkip;
break;
case ol_sec_type_tkip_nomic:
peer_sec_type = htt_sec_type_tkip_nomic;
break;
case ol_sec_type_aes_ccmp:
peer_sec_type = htt_sec_type_aes_ccmp;
break;
case ol_sec_type_wapi:
peer_sec_type = htt_sec_type_wapi;
break;
default:
peer_sec_type = htt_sec_type_none;
break;
}
peer->security[txrx_sec_ucast].sec_type =
peer->security[txrx_sec_mcast].sec_type =
peer_sec_type;
break;
}
default:
{
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"ERROR: unknown param %d in %s", select,
__func__);
break;
}
} /* switch */
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_INTERNAL);
}
uint8_t
ol_txrx_peer_uapsdmask_get(struct ol_txrx_pdev_t *txrx_pdev, uint16_t peer_id)
{
struct ol_txrx_peer_t *peer;
peer = ol_txrx_peer_find_by_id(txrx_pdev, peer_id);
if (peer)
return peer->uapsd_mask;
return 0;
}
uint8_t
ol_txrx_peer_qoscapable_get(struct ol_txrx_pdev_t *txrx_pdev, uint16_t peer_id)
{
struct ol_txrx_peer_t *peer_t =
ol_txrx_peer_find_by_id(txrx_pdev, peer_id);
if (peer_t)
return peer_t->qos_capable;
return 0;
}
/**
* ol_txrx_peer_free_tids() - free tids for the peer
* @peer: peer handle
*
* Return: None
*/
static inline void ol_txrx_peer_free_tids(ol_txrx_peer_handle peer)
{
int i = 0;
/*
* 'array' is allocated in addba handler and is supposed to be
* freed in delba handler. There is the case (for example, in
* SSR) where delba handler is not called. Because array points
* to address of 'base' by default and is reallocated in addba
* handler later, only free the memory when the array does not
* point to base.
*/
for (i = 0; i < OL_TXRX_NUM_EXT_TIDS; i++) {
if (peer->tids_rx_reorder[i].array !=
&peer->tids_rx_reorder[i].base) {
ol_txrx_dbg("delete reorder arr, tid:%d", i);
qdf_mem_free(peer->tids_rx_reorder[i].array);
ol_rx_reorder_init(&peer->tids_rx_reorder[i],
(uint8_t)i);
}
}
}
/**
* ol_txrx_peer_release_ref() - release peer reference
* @peer: peer handle
*
* Release peer reference and delete peer if refcount is 0
*
* Return: Resulting peer ref_cnt after this function is invoked
*/
int ol_txrx_peer_release_ref(ol_txrx_peer_handle peer,
enum peer_debug_id_type debug_id)
{
int rc;
struct ol_txrx_vdev_t *vdev;
struct ol_txrx_pdev_t *pdev;
bool ref_silent = true;
int access_list = 0;
uint32_t err_code = 0;
/* preconditions */
TXRX_ASSERT2(peer);
vdev = peer->vdev;
if (!vdev) {
ol_txrx_err("The vdev is not present anymore\n");
return -EINVAL;
}
pdev = vdev->pdev;
if (!pdev) {
ol_txrx_err("The pdev is not present anymore\n");
err_code = 0xbad2;
goto ERR_STATE;
}
if (debug_id >= PEER_DEBUG_ID_MAX || debug_id < 0) {
ol_txrx_err("incorrect debug_id %d ", debug_id);
err_code = 0xbad3;
goto ERR_STATE;
}
if (debug_id == PEER_DEBUG_ID_OL_RX_THREAD)
ref_silent = true;
if (!ref_silent)
wlan_roam_debug_log(vdev->vdev_id, DEBUG_PEER_UNREF_DELETE,
DEBUG_INVALID_PEER_ID, &peer->mac_addr.raw,
peer, 0xdead,
qdf_atomic_read(&peer->ref_cnt));
/*
* Hold the lock all the way from checking if the peer ref count
* is zero until the peer references are removed from the hash
* table and vdev list (if the peer ref count is zero).
* This protects against a new HL tx operation starting to use the
* peer object just after this function concludes it's done being used.
* Furthermore, the lock needs to be held while checking whether the
* vdev's list of peers is empty, to make sure that list is not modified
* concurrently with the empty check.
*/
qdf_spin_lock_bh(&pdev->peer_ref_mutex);
/*
* Check for the reference count before deleting the peer
* as we noticed that sometimes we are re-entering this
* function again which is leading to dead-lock.
* (A double-free should never happen, so assert if it does.)
*/
rc = qdf_atomic_read(&(peer->ref_cnt));
if (rc == 0) {
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
ol_txrx_err("The Peer is not present anymore\n");
qdf_assert(0);
return -EACCES;
}
/*
* now decrement rc; this will be the return code.
* 0 : peer deleted
* >0: peer ref removed, but still has other references
* <0: sanity failed - no changes to the state of the peer
*/
rc--;
if (!qdf_atomic_read(&peer->access_list[debug_id])) {
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
ol_txrx_err("peer %pK ref was not taken by %d",
peer, debug_id);
ol_txrx_dump_peer_access_list(peer);
QDF_BUG(0);
return -EACCES;
}
qdf_atomic_dec(&peer->access_list[debug_id]);
if (qdf_atomic_dec_and_test(&peer->ref_cnt)) {
u16 peer_id;
wlan_roam_debug_log(vdev->vdev_id,
DEBUG_DELETING_PEER_OBJ,
DEBUG_INVALID_PEER_ID,
&peer->mac_addr.raw, peer, 0,
qdf_atomic_read(&peer->ref_cnt));
peer_id = peer->local_id;
/* remove the reference to the peer from the hash table */
ol_txrx_peer_find_hash_remove(pdev, peer);
/* remove the peer from its parent vdev's list */
TAILQ_REMOVE(&peer->vdev->peer_list, peer, peer_list_elem);
/* cleanup the Rx reorder queues for this peer */
ol_rx_peer_cleanup(vdev, peer);
qdf_spinlock_destroy(&peer->peer_info_lock);
qdf_spinlock_destroy(&peer->bufq_info.bufq_lock);
/* peer is removed from peer_list */
qdf_atomic_set(&peer->delete_in_progress, 0);
/*
* Set wait_delete_comp event if the current peer id matches
* with registered peer id.
*/
if (peer_id == vdev->wait_on_peer_id) {
qdf_event_set(&vdev->wait_delete_comp);
vdev->wait_on_peer_id = OL_TXRX_INVALID_LOCAL_PEER_ID;
}
qdf_timer_sync_cancel(&peer->peer_unmap_timer);
qdf_timer_free(&peer->peer_unmap_timer);
/* check whether the parent vdev has no peers left */
if (TAILQ_EMPTY(&vdev->peer_list)) {
/*
* Check if the parent vdev was waiting for its peers
* to be deleted, in order for it to be deleted too.
*/
if (vdev->delete.pending) {
ol_txrx_vdev_delete_cb vdev_delete_cb =
vdev->delete.callback;
void *vdev_delete_context =
vdev->delete.context;
/*
* Now that there are no references to the peer,
* we can release the peer reference lock.
*/
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
/*
* The ol_tx_desc_free might access the invalid
* content of vdev referred by tx desc, since
* this vdev might be detached in another thread
* asynchronous.
*
* Go through tx desc pool to set corresponding
* tx desc's vdev to NULL when detach this vdev,
* and add vdev checking in the ol_tx_desc_free
* to avoid crash.
*/
ol_txrx_tx_desc_reset_vdev(vdev);
ol_txrx_dbg(
"deleting vdev object %pK ("QDF_MAC_ADDR_STR") - its last peer is done",
vdev,
QDF_MAC_ADDR_ARRAY(vdev->mac_addr.raw));
/* all peers are gone, go ahead and delete it */
qdf_mem_free(vdev);
if (vdev_delete_cb)
vdev_delete_cb(vdev_delete_context);
} else {
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
}
} else {
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
}
ol_txrx_info_high("[%d][%d]: Deleting peer %pK ref_cnt -> %d %s",
debug_id,
qdf_atomic_read(&peer->access_list[debug_id]),
peer, rc,
qdf_atomic_read(&peer->fw_create_pending)
== 1 ?
"(No Maps received)" : "");
ol_txrx_peer_tx_queue_free(pdev, peer);
/* Remove mappings from peer_id to peer object */
ol_txrx_peer_clear_map_peer(pdev, peer);
/* Remove peer pointer from local peer ID map */
ol_txrx_local_peer_id_free(pdev, peer);
ol_txrx_peer_free_tids(peer);
ol_txrx_dump_peer_access_list(peer);
if (QDF_GLOBAL_MONITOR_MODE == cds_get_conparam() &&
pdev->self_peer == peer)
pdev->self_peer = NULL;
qdf_mem_free(peer);
} else {
access_list = qdf_atomic_read(&peer->access_list[debug_id]);
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
if (!ref_silent)
ol_txrx_info_high("[%d][%d]: ref delete peer %pK ref_cnt -> %d",
debug_id,
access_list,
peer, rc);
}
return rc;
ERR_STATE:
wlan_roam_debug_log(vdev->vdev_id, DEBUG_PEER_UNREF_DELETE,
DEBUG_INVALID_PEER_ID, &peer->mac_addr.raw,
peer, err_code, qdf_atomic_read(&peer->ref_cnt));
return -EINVAL;
}
/**
* ol_txrx_clear_peer_internal() - ol internal function to clear peer
* @peer: pointer to ol txrx peer structure
*
* Return: QDF Status
*/
static QDF_STATUS
ol_txrx_clear_peer_internal(struct ol_txrx_peer_t *peer)
{
p_cds_sched_context sched_ctx = get_cds_sched_ctxt();
/* Drop pending Rx frames in CDS */
if (sched_ctx)
cds_drop_rxpkt_by_staid(sched_ctx, peer->local_id);
/* Purge the cached rx frame queue */
ol_txrx_flush_rx_frames(peer, 1);
qdf_spin_lock_bh(&peer->peer_info_lock);
peer->state = OL_TXRX_PEER_STATE_DISC;
qdf_spin_unlock_bh(&peer->peer_info_lock);
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_clear_peer() - clear peer
* peer_addr: peer mac address
*
* Return: QDF Status
*/
static QDF_STATUS
ol_txrx_clear_peer(struct cdp_pdev *ppdev,
struct qdf_mac_addr peer_addr)
{
struct ol_txrx_peer_t *peer;
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
QDF_STATUS status;
/* peer_id to be removed */
uint8_t peer_id;
if (!pdev) {
ol_txrx_err("Unable to find pdev!");
return QDF_STATUS_E_FAILURE;
}
peer = ol_txrx_peer_get_ref_by_addr(pdev, peer_addr.bytes, &peer_id,
PEER_DEBUG_ID_OL_INTERNAL);
/* Return success, if the peer is already cleared by
* data path via peer detach function.
*/
if (!peer)
return QDF_STATUS_SUCCESS;
ol_txrx_dbg("Clear peer rx frames: " QDF_MAC_ADDR_STR,
QDF_MAC_ADDR_ARRAY(peer->mac_addr.raw));
ol_txrx_clear_peer_internal(peer);
status = ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_INTERNAL);
return status;
}
/**
* peer_unmap_timer_handler() - peer unmap timer function
* @data: peer object pointer
*
* Return: none
*/
void peer_unmap_timer_handler(void *data)
{
ol_txrx_peer_handle peer = (ol_txrx_peer_handle)data;
if (!peer)
return;
ol_txrx_err("all unmap events not received for peer %pK, ref_cnt %d",
peer, qdf_atomic_read(&peer->ref_cnt));
ol_txrx_err("peer %pK ("QDF_MAC_ADDR_STR")",
peer,
QDF_MAC_ADDR_ARRAY(peer->mac_addr.raw));
cds_trigger_recovery(QDF_PEER_UNMAP_TIMEDOUT);
}
/**
* ol_txrx_peer_detach() - Delete a peer's data object.
* @peer - the object to detach
* @bitmap - bitmap indicating special handling of request.
*
* When the host's control SW disassociates a peer, it calls
* this function to detach and delete the peer. The reference
* stored in the control peer object to the data peer
* object (set up by a call to ol_peer_store()) is provided.
*
* Return: None
*/
static void ol_txrx_peer_detach(void *ppeer, uint32_t bitmap)
{
ol_txrx_peer_handle peer = ppeer;
struct ol_txrx_vdev_t *vdev = peer->vdev;
/* redirect peer's rx delivery function to point to a discard func */
peer->rx_opt_proc = ol_rx_discard;
peer->valid = 0;
/* flush all rx packets before clearing up the peer local_id */
ol_txrx_clear_peer_internal(peer);
/* debug print to dump rx reorder state */
/* htt_rx_reorder_log_print(vdev->pdev->htt_pdev); */
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_DEBUG,
"%s:peer %pK ("QDF_MAC_ADDR_STR")",
__func__, peer,
QDF_MAC_ADDR_ARRAY(peer->mac_addr.raw));
qdf_spin_lock_bh(&vdev->pdev->last_real_peer_mutex);
if (vdev->last_real_peer == peer)
vdev->last_real_peer = NULL;
qdf_spin_unlock_bh(&vdev->pdev->last_real_peer_mutex);
htt_rx_reorder_log_print(peer->vdev->pdev->htt_pdev);
/*
* set delete_in_progress to identify that wma
* is waiting for unmap massage for this peer
*/
qdf_atomic_set(&peer->delete_in_progress, 1);
if (!(bitmap & (1 << CDP_PEER_DO_NOT_START_UNMAP_TIMER))) {
if (vdev->opmode == wlan_op_mode_sta) {
qdf_mem_copy(&peer->vdev->last_peer_mac_addr,
&peer->mac_addr,
sizeof(union ol_txrx_align_mac_addr_t));
/*
* Create a timer to track unmap events when the
* sta peer gets deleted.
*/
qdf_timer_start(&peer->peer_unmap_timer,
OL_TXRX_PEER_UNMAP_TIMEOUT);
ol_txrx_info_high
("started peer_unmap_timer for peer %pK",
peer);
}
}
/*
* Remove the reference added during peer_attach.
* The peer will still be left allocated until the
* PEER_UNMAP message arrives to remove the other
* reference, added by the PEER_MAP message.
*/
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_PEER_ATTACH);
}
/**
* ol_txrx_peer_detach_force_delete() - Detach and delete a peer's data object
* @ppeer - the object to detach
*
* Detach a peer and force peer object to be removed. It is called during
* roaming scenario when the firmware has already deleted a peer.
* Remove it from the peer_id_to_object map. Peer object is actually freed
* when last reference is deleted.
*
* Return: None
*/
static void ol_txrx_peer_detach_force_delete(void *ppeer)
{
ol_txrx_peer_handle peer = ppeer;
ol_txrx_pdev_handle pdev = peer->vdev->pdev;
ol_txrx_info_high("peer %pK, peer->ref_cnt %d",
peer, qdf_atomic_read(&peer->ref_cnt));
/* Clear the peer_id_to_obj map entries */
ol_txrx_peer_remove_obj_map_entries(pdev, peer);
ol_txrx_peer_detach(peer, 1 << CDP_PEER_DELETE_NO_SPECIAL);
}
/**
* ol_txrx_peer_detach_sync() - peer detach sync callback
* @ppeer - the peer object
* @peer_unmap_sync - peer unmap sync cb.
* @bitmap - bitmap indicating special handling of request.
*
* Return: None
*/
static void ol_txrx_peer_detach_sync(void *ppeer,
ol_txrx_peer_unmap_sync_cb peer_unmap_sync,
uint32_t bitmap)
{
ol_txrx_peer_handle peer = ppeer;
ol_txrx_pdev_handle pdev = peer->vdev->pdev;
ol_txrx_info_high("%s peer %pK, peer->ref_cnt %d", __func__,
peer, qdf_atomic_read(&peer->ref_cnt));
if (!pdev->peer_unmap_sync_cb)
pdev->peer_unmap_sync_cb = peer_unmap_sync;
ol_txrx_peer_detach(peer, bitmap);
}
/**
* ol_txrx_peer_unmap_sync_cb_set() - set peer unmap sync callback
* @ppdev - TXRX pdev context
* @peer_unmap_sync - peer unmap sync callback
*
* Return: None
*/
static void ol_txrx_peer_unmap_sync_cb_set(
struct cdp_pdev *ppdev,
ol_txrx_peer_unmap_sync_cb peer_unmap_sync)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
if (!pdev->peer_unmap_sync_cb)
pdev->peer_unmap_sync_cb = peer_unmap_sync;
}
/**
* ol_txrx_dump_tx_desc() - dump tx desc total and free count
* @txrx_pdev: Pointer to txrx pdev
*
* Return: none
*/
static void ol_txrx_dump_tx_desc(ol_txrx_pdev_handle pdev_handle)
{
struct ol_txrx_pdev_t *pdev = (ol_txrx_pdev_handle) pdev_handle;
uint32_t total, num_free;
if (ol_cfg_is_high_latency(pdev->ctrl_pdev))
total = qdf_atomic_read(&pdev->orig_target_tx_credit);
else
total = ol_tx_get_desc_global_pool_size(pdev);
num_free = ol_tx_get_total_free_desc(pdev);
ol_txrx_info_high(
"total tx credit %d num_free %d",
total, num_free);
}
/**
* ol_txrx_wait_for_pending_tx() - wait for tx queue to be empty
* @timeout: timeout in ms
*
* Wait for tx queue to be empty, return timeout error if
* queue doesn't empty before timeout occurs.
*
* Return:
* QDF_STATUS_SUCCESS if the queue empties,
* QDF_STATUS_E_TIMEOUT in case of timeout,
* QDF_STATUS_E_FAULT in case of missing handle
*/
static QDF_STATUS ol_txrx_wait_for_pending_tx(int timeout)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
struct ol_txrx_pdev_t *txrx_pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return QDF_STATUS_E_FAULT;
}
txrx_pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!txrx_pdev) {
ol_txrx_err("txrx context is null");
return QDF_STATUS_E_FAULT;
}
while (ol_txrx_get_tx_pending((struct cdp_pdev *)txrx_pdev)) {
qdf_sleep(OL_ATH_TX_DRAIN_WAIT_DELAY);
if (timeout <= 0) {
ol_txrx_err("tx frames are pending");
ol_txrx_dump_tx_desc(txrx_pdev);
return QDF_STATUS_E_TIMEOUT;
}
timeout = timeout - OL_ATH_TX_DRAIN_WAIT_DELAY;
}
return QDF_STATUS_SUCCESS;
}
#ifndef QCA_WIFI_3_0_EMU
#define SUSPEND_DRAIN_WAIT 500
#else
#define SUSPEND_DRAIN_WAIT 3000
#endif
#ifdef FEATURE_RUNTIME_PM
/**
* ol_txrx_runtime_suspend() - ensure TXRX is ready to runtime suspend
* @txrx_pdev: TXRX pdev context
*
* TXRX is ready to runtime suspend if there are no pending packets
* in the tx queue.
*
* Return: QDF_STATUS
*/
static QDF_STATUS ol_txrx_runtime_suspend(struct cdp_pdev *ppdev)
{
struct ol_txrx_pdev_t *txrx_pdev = (struct ol_txrx_pdev_t *)ppdev;
if (ol_txrx_get_tx_pending((struct cdp_pdev *)txrx_pdev))
return QDF_STATUS_E_BUSY;
else
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_runtime_resume() - ensure TXRX is ready to runtime resume
* @txrx_pdev: TXRX pdev context
*
* This is a dummy function for symmetry.
*
* Return: QDF_STATUS_SUCCESS
*/
static QDF_STATUS ol_txrx_runtime_resume(struct cdp_pdev *ppdev)
{
return QDF_STATUS_SUCCESS;
}
#endif
/**
* ol_txrx_bus_suspend() - bus suspend
* @ppdev: TXRX pdev context
*
* Ensure that ol_txrx is ready for bus suspend
*
* Return: QDF_STATUS
*/
static QDF_STATUS ol_txrx_bus_suspend(struct cdp_pdev *ppdev)
{
return ol_txrx_wait_for_pending_tx(SUSPEND_DRAIN_WAIT);
}
/**
* ol_txrx_bus_resume() - bus resume
* @ppdev: TXRX pdev context
*
* Dummy function for symetry
*
* Return: QDF_STATUS_SUCCESS
*/
static QDF_STATUS ol_txrx_bus_resume(struct cdp_pdev *ppdev)
{
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_get_tx_pending - Get the number of pending transmit
* frames that are awaiting completion.
*
* @pdev - the data physical device object
* Mainly used in clean up path to make sure all buffers have been freed
*
* Return: count of pending frames
*/
int ol_txrx_get_tx_pending(struct cdp_pdev *ppdev)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
uint32_t total;
if (ol_cfg_is_high_latency(pdev->ctrl_pdev))
total = qdf_atomic_read(&pdev->orig_target_tx_credit);
else
total = ol_tx_get_desc_global_pool_size(pdev);
return total - ol_tx_get_total_free_desc(pdev);
}
void ol_txrx_discard_tx_pending(ol_txrx_pdev_handle pdev_handle)
{
ol_tx_desc_list tx_descs;
/*
* First let hif do the qdf_atomic_dec_and_test(&tx_desc->ref_cnt)
* then let htt do the qdf_atomic_dec_and_test(&tx_desc->ref_cnt)
* which is tha same with normal data send complete path
*/
htt_tx_pending_discard(pdev_handle->htt_pdev);
TAILQ_INIT(&tx_descs);
ol_tx_queue_discard(pdev_handle, true, &tx_descs);
/* Discard Frames in Discard List */
ol_tx_desc_frame_list_free(pdev_handle, &tx_descs, 1 /* error */);
ol_tx_discard_target_frms(pdev_handle);
}
static inline
uint64_t ol_txrx_stats_ptr_to_u64(struct ol_txrx_stats_req_internal *req)
{
return (uint64_t) ((size_t) req);
}
static inline
struct ol_txrx_stats_req_internal *ol_txrx_u64_to_stats_ptr(uint64_t cookie)
{
return (struct ol_txrx_stats_req_internal *)((size_t) cookie);
}
#ifdef currently_unused
void
ol_txrx_fw_stats_cfg(ol_txrx_vdev_handle vdev,
uint8_t cfg_stats_type, uint32_t cfg_val)
{
uint8_t dummy_cookie = 0;
htt_h2t_dbg_stats_get(vdev->pdev->htt_pdev, 0 /* upload mask */,
0 /* reset mask */,
cfg_stats_type, cfg_val, dummy_cookie);
}
#endif
/**
* ol_txrx_fw_stats_desc_pool_init() - Initialize the fw stats descriptor pool
* @pdev: handle to ol txrx pdev
* @pool_size: Size of fw stats descriptor pool
*
* Return: 0 for success, error code on failure.
*/
int ol_txrx_fw_stats_desc_pool_init(struct ol_txrx_pdev_t *pdev,
uint8_t pool_size)
{
int i;
if (!pdev) {
ol_txrx_err("pdev is NULL");
return -EINVAL;
}
pdev->ol_txrx_fw_stats_desc_pool.pool = qdf_mem_malloc(pool_size *
sizeof(struct ol_txrx_fw_stats_desc_elem_t));
if (!pdev->ol_txrx_fw_stats_desc_pool.pool)
return -ENOMEM;
pdev->ol_txrx_fw_stats_desc_pool.freelist =
&pdev->ol_txrx_fw_stats_desc_pool.pool[0];
pdev->ol_txrx_fw_stats_desc_pool.pool_size = pool_size;
for (i = 0; i < (pool_size - 1); i++) {
pdev->ol_txrx_fw_stats_desc_pool.pool[i].desc.desc_id = i;
pdev->ol_txrx_fw_stats_desc_pool.pool[i].desc.req = NULL;
pdev->ol_txrx_fw_stats_desc_pool.pool[i].next =
&pdev->ol_txrx_fw_stats_desc_pool.pool[i + 1];
}
pdev->ol_txrx_fw_stats_desc_pool.pool[i].desc.desc_id = i;
pdev->ol_txrx_fw_stats_desc_pool.pool[i].desc.req = NULL;
pdev->ol_txrx_fw_stats_desc_pool.pool[i].next = NULL;
qdf_spinlock_create(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
qdf_atomic_init(&pdev->ol_txrx_fw_stats_desc_pool.initialized);
qdf_atomic_set(&pdev->ol_txrx_fw_stats_desc_pool.initialized, 1);
return 0;
}
/**
* ol_txrx_fw_stats_desc_pool_deinit() - Deinitialize the
* fw stats descriptor pool
* @pdev: handle to ol txrx pdev
*
* Return: None
*/
void ol_txrx_fw_stats_desc_pool_deinit(struct ol_txrx_pdev_t *pdev)
{
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
if (!qdf_atomic_read(&pdev->ol_txrx_fw_stats_desc_pool.initialized)) {
ol_txrx_err("Pool is not initialized");
return;
}
if (!pdev->ol_txrx_fw_stats_desc_pool.pool) {
ol_txrx_err("Pool is not allocated");
return;
}
qdf_spin_lock_bh(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
qdf_atomic_set(&pdev->ol_txrx_fw_stats_desc_pool.initialized, 0);
qdf_mem_free(pdev->ol_txrx_fw_stats_desc_pool.pool);
pdev->ol_txrx_fw_stats_desc_pool.pool = NULL;
pdev->ol_txrx_fw_stats_desc_pool.freelist = NULL;
pdev->ol_txrx_fw_stats_desc_pool.pool_size = 0;
qdf_spin_unlock_bh(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
}
/**
* ol_txrx_fw_stats_desc_alloc() - Get fw stats descriptor from fw stats
* free descriptor pool
* @pdev: handle to ol txrx pdev
*
* Return: pointer to fw stats descriptor, NULL on failure
*/
struct ol_txrx_fw_stats_desc_t
*ol_txrx_fw_stats_desc_alloc(struct ol_txrx_pdev_t *pdev)
{
struct ol_txrx_fw_stats_desc_t *desc = NULL;
qdf_spin_lock_bh(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
if (!qdf_atomic_read(&pdev->ol_txrx_fw_stats_desc_pool.initialized)) {
qdf_spin_unlock_bh(&pdev->
ol_txrx_fw_stats_desc_pool.pool_lock);
ol_txrx_err("Pool deinitialized");
return NULL;
}
if (pdev->ol_txrx_fw_stats_desc_pool.freelist) {
desc = &pdev->ol_txrx_fw_stats_desc_pool.freelist->desc;
pdev->ol_txrx_fw_stats_desc_pool.freelist =
pdev->ol_txrx_fw_stats_desc_pool.freelist->next;
}
qdf_spin_unlock_bh(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
if (desc)
ol_txrx_dbg("desc_id %d allocated", desc->desc_id);
else
ol_txrx_err("fw stats descriptors are exhausted");
return desc;
}
/**
* ol_txrx_fw_stats_desc_get_req() - Put fw stats descriptor
* back into free pool
* @pdev: handle to ol txrx pdev
* @fw_stats_desc: fw_stats_desc_get descriptor
*
* Return: pointer to request
*/
struct ol_txrx_stats_req_internal
*ol_txrx_fw_stats_desc_get_req(struct ol_txrx_pdev_t *pdev,
unsigned char desc_id)
{
struct ol_txrx_fw_stats_desc_elem_t *desc_elem;
struct ol_txrx_stats_req_internal *req;
qdf_spin_lock_bh(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
if (!qdf_atomic_read(&pdev->ol_txrx_fw_stats_desc_pool.initialized)) {
qdf_spin_unlock_bh(&pdev->
ol_txrx_fw_stats_desc_pool.pool_lock);
ol_txrx_err("Desc ID %u Pool deinitialized", desc_id);
return NULL;
}
desc_elem = &pdev->ol_txrx_fw_stats_desc_pool.pool[desc_id];
req = desc_elem->desc.req;
desc_elem->desc.req = NULL;
desc_elem->next =
pdev->ol_txrx_fw_stats_desc_pool.freelist;
pdev->ol_txrx_fw_stats_desc_pool.freelist = desc_elem;
qdf_spin_unlock_bh(&pdev->ol_txrx_fw_stats_desc_pool.pool_lock);
return req;
}
static A_STATUS
ol_txrx_fw_stats_get(struct cdp_vdev *pvdev, struct ol_txrx_stats_req *req,
bool per_vdev, bool response_expected)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
struct ol_txrx_pdev_t *pdev = vdev->pdev;
uint8_t cookie = FW_STATS_DESC_POOL_SIZE;
struct ol_txrx_stats_req_internal *non_volatile_req;
struct ol_txrx_fw_stats_desc_t *desc = NULL;
struct ol_txrx_fw_stats_desc_elem_t *elem = NULL;
if (!pdev ||
req->stats_type_upload_mask >= 1 << HTT_DBG_NUM_STATS ||
req->stats_type_reset_mask >= 1 << HTT_DBG_NUM_STATS) {
return A_ERROR;
}
/*
* Allocate a non-transient stats request object.
* (The one provided as an argument is likely allocated on the stack.)
*/
non_volatile_req = qdf_mem_malloc(sizeof(*non_volatile_req));
if (!non_volatile_req)
return A_NO_MEMORY;
/* copy the caller's specifications */
non_volatile_req->base = *req;
non_volatile_req->serviced = 0;
non_volatile_req->offset = 0;
if (response_expected) {
desc = ol_txrx_fw_stats_desc_alloc(pdev);
if (!desc) {
qdf_mem_free(non_volatile_req);
return A_ERROR;
}
/* use the desc id as the cookie */
cookie = desc->desc_id;
desc->req = non_volatile_req;
qdf_spin_lock_bh(&pdev->req_list_spinlock);
TAILQ_INSERT_TAIL(&pdev->req_list, non_volatile_req, req_list_elem);
pdev->req_list_depth++;
qdf_spin_unlock_bh(&pdev->req_list_spinlock);
}
if (htt_h2t_dbg_stats_get(pdev->htt_pdev,
req->stats_type_upload_mask,
req->stats_type_reset_mask,
HTT_H2T_STATS_REQ_CFG_STAT_TYPE_INVALID, 0,
cookie)) {
if (response_expected) {
qdf_spin_lock_bh(&pdev->req_list_spinlock);
TAILQ_REMOVE(&pdev->req_list, non_volatile_req,
req_list_elem);
pdev->req_list_depth--;
qdf_spin_unlock_bh(&pdev->req_list_spinlock);
if (desc) {
qdf_spin_lock_bh(&pdev->ol_txrx_fw_stats_desc_pool.
pool_lock);
desc->req = NULL;
elem = container_of(desc,
struct ol_txrx_fw_stats_desc_elem_t,
desc);
elem->next =
pdev->ol_txrx_fw_stats_desc_pool.freelist;
pdev->ol_txrx_fw_stats_desc_pool.freelist = elem;
qdf_spin_unlock_bh(&pdev->
ol_txrx_fw_stats_desc_pool.
pool_lock);
}
}
qdf_mem_free(non_volatile_req);
return A_ERROR;
}
if (response_expected == false)
qdf_mem_free(non_volatile_req);
return A_OK;
}
void
ol_txrx_fw_stats_handler(ol_txrx_pdev_handle pdev,
uint8_t cookie, uint8_t *stats_info_list)
{
enum htt_dbg_stats_type type;
enum htt_cmn_dbg_stats_type cmn_type = HTT_DBG_CMN_NUM_STATS_INVALID;
enum htt_dbg_stats_status status;
int length;
uint8_t *stats_data;
struct ol_txrx_stats_req_internal *req, *tmp;
int more = 0;
int found = 0;
if (cookie >= FW_STATS_DESC_POOL_SIZE) {
ol_txrx_err("Cookie is not valid");
return;
}
req = ol_txrx_fw_stats_desc_get_req(pdev, (uint8_t)cookie);
if (!req) {
ol_txrx_err("%s: Request not retrieved for cookie %u", __func__,
(uint8_t)cookie);
return;
}
qdf_spin_lock_bh(&pdev->req_list_spinlock);
TAILQ_FOREACH(tmp, &pdev->req_list, req_list_elem) {
if (req == tmp) {
found = 1;
break;
}
}
qdf_spin_unlock_bh(&pdev->req_list_spinlock);
if (!found) {
ol_txrx_err(
"req(%pK) from firmware can't be found in the list\n", req);
return;
}
do {
htt_t2h_dbg_stats_hdr_parse(stats_info_list, &type, &status,
&length, &stats_data);
if (status == HTT_DBG_STATS_STATUS_SERIES_DONE)
break;
if (status == HTT_DBG_STATS_STATUS_PRESENT ||
status == HTT_DBG_STATS_STATUS_PARTIAL) {
uint8_t *buf;
int bytes = 0;
if (status == HTT_DBG_STATS_STATUS_PARTIAL)
more = 1;
if (req->base.print.verbose || req->base.print.concise)
/* provide the header along with the data */
htt_t2h_stats_print(stats_info_list,
req->base.print.concise);
switch (type) {
case HTT_DBG_STATS_WAL_PDEV_TXRX:
bytes = sizeof(struct wlan_dbg_stats);
if (req->base.copy.buf) {
int lmt;
lmt = sizeof(struct wlan_dbg_stats);
if (req->base.copy.byte_limit < lmt)
lmt = req->base.copy.byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, lmt);
}
break;
case HTT_DBG_STATS_RX_REORDER:
bytes = sizeof(struct rx_reorder_stats);
if (req->base.copy.buf) {
int lmt;
lmt = sizeof(struct rx_reorder_stats);
if (req->base.copy.byte_limit < lmt)
lmt = req->base.copy.byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, lmt);
}
break;
case HTT_DBG_STATS_RX_RATE_INFO:
bytes = sizeof(wlan_dbg_rx_rate_info_t);
if (req->base.copy.buf) {
int lmt;
lmt = sizeof(wlan_dbg_rx_rate_info_t);
if (req->base.copy.byte_limit < lmt)
lmt = req->base.copy.byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, lmt);
}
break;
case HTT_DBG_STATS_TX_RATE_INFO:
bytes = sizeof(wlan_dbg_tx_rate_info_t);
if (req->base.copy.buf) {
int lmt;
lmt = sizeof(wlan_dbg_tx_rate_info_t);
if (req->base.copy.byte_limit < lmt)
lmt = req->base.copy.byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, lmt);
}
break;
case HTT_DBG_STATS_TX_PPDU_LOG:
bytes = 0;
/* TO DO: specify how many bytes are present */
/* TO DO: add copying to the requestor's buf */
case HTT_DBG_STATS_RX_REMOTE_RING_BUFFER_INFO:
bytes = sizeof(struct
rx_remote_buffer_mgmt_stats);
if (req->base.copy.buf) {
int limit;
limit = sizeof(struct
rx_remote_buffer_mgmt_stats);
if (req->base.copy.byte_limit < limit)
limit = req->base.copy.
byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, limit);
}
break;
case HTT_DBG_STATS_TXBF_INFO:
bytes = sizeof(struct wlan_dbg_txbf_data_stats);
if (req->base.copy.buf) {
int limit;
limit = sizeof(struct
wlan_dbg_txbf_data_stats);
if (req->base.copy.byte_limit < limit)
limit = req->base.copy.
byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, limit);
}
break;
case HTT_DBG_STATS_SND_INFO:
bytes = sizeof(struct wlan_dbg_txbf_snd_stats);
if (req->base.copy.buf) {
int limit;
limit = sizeof(struct
wlan_dbg_txbf_snd_stats);
if (req->base.copy.byte_limit < limit)
limit = req->base.copy.
byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, limit);
}
break;
case HTT_DBG_STATS_TX_SELFGEN_INFO:
bytes = sizeof(struct
wlan_dbg_tx_selfgen_stats);
if (req->base.copy.buf) {
int limit;
limit = sizeof(struct
wlan_dbg_tx_selfgen_stats);
if (req->base.copy.byte_limit < limit)
limit = req->base.copy.
byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, limit);
}
break;
case HTT_DBG_STATS_ERROR_INFO:
bytes =
sizeof(struct wlan_dbg_wifi2_error_stats);
if (req->base.copy.buf) {
int limit;
limit = sizeof(struct
wlan_dbg_wifi2_error_stats);
if (req->base.copy.byte_limit < limit)
limit = req->base.copy.
byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, limit);
}
break;
case HTT_DBG_STATS_TXBF_MUSU_NDPA_PKT:
bytes =
sizeof(struct rx_txbf_musu_ndpa_pkts_stats);
if (req->base.copy.buf) {
int limit;
limit = sizeof(struct
rx_txbf_musu_ndpa_pkts_stats);
if (req->base.copy.byte_limit < limit)
limit =
req->base.copy.byte_limit;
buf = req->base.copy.buf + req->offset;
qdf_mem_copy(buf, stats_data, limit);
}
break;
default:
break;
}
buf = req->base.copy.buf ?
req->base.copy.buf : stats_data;
/* Not implemented for MCL */
if (req->base.callback.fp)
req->base.callback.fp(req->base.callback.ctxt,
cmn_type, buf, bytes);
}
stats_info_list += length;
} while (1);
if (!more) {
qdf_spin_lock_bh(&pdev->req_list_spinlock);
TAILQ_FOREACH(tmp, &pdev->req_list, req_list_elem) {
if (req == tmp) {
TAILQ_REMOVE(&pdev->req_list, req, req_list_elem);
pdev->req_list_depth--;
qdf_mem_free(req);
break;
}
}
qdf_spin_unlock_bh(&pdev->req_list_spinlock);
}
}
#ifndef ATH_PERF_PWR_OFFLOAD /*---------------------------------------------*/
int ol_txrx_debug(ol_txrx_vdev_handle vdev, int debug_specs)
{
if (debug_specs & TXRX_DBG_MASK_OBJS) {
#if defined(TXRX_DEBUG_LEVEL) && TXRX_DEBUG_LEVEL > 5
ol_txrx_pdev_display(vdev->pdev, 0);
#else
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_FATAL,
"The pdev,vdev,peer display functions are disabled.\n To enable them, recompile with TXRX_DEBUG_LEVEL > 5");
#endif
}
if (debug_specs & TXRX_DBG_MASK_STATS)
ol_txrx_stats_display(vdev->pdev,
QDF_STATS_VERBOSITY_LEVEL_HIGH);
if (debug_specs & TXRX_DBG_MASK_PROT_ANALYZE) {
#if defined(ENABLE_TXRX_PROT_ANALYZE)
ol_txrx_prot_ans_display(vdev->pdev);
#else
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_FATAL,
"txrx protocol analysis is disabled.\n To enable it, recompile with ENABLE_TXRX_PROT_ANALYZE defined");
#endif
}
if (debug_specs & TXRX_DBG_MASK_RX_REORDER_TRACE) {
#if defined(ENABLE_RX_REORDER_TRACE)
ol_rx_reorder_trace_display(vdev->pdev, 0, 0);
#else
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_FATAL,
"rx reorder seq num trace is disabled.\n To enable it, recompile with ENABLE_RX_REORDER_TRACE defined");
#endif
}
return 0;
}
#endif
#ifdef currently_unused
int ol_txrx_aggr_cfg(ol_txrx_vdev_handle vdev,
int max_subfrms_ampdu, int max_subfrms_amsdu)
{
return htt_h2t_aggr_cfg_msg(vdev->pdev->htt_pdev,
max_subfrms_ampdu, max_subfrms_amsdu);
}
#endif
#if defined(TXRX_DEBUG_LEVEL) && TXRX_DEBUG_LEVEL > 5
void ol_txrx_pdev_display(ol_txrx_pdev_handle pdev, int indent)
{
struct ol_txrx_vdev_t *vdev;
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*s%s:\n", indent, " ", "txrx pdev");
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*spdev object: %pK", indent + 4, " ", pdev);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*svdev list:", indent + 4, " ");
TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) {
ol_txrx_vdev_display(vdev, indent + 8);
}
ol_txrx_peer_find_display(pdev, indent + 4);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*stx desc pool: %d elems @ %pK", indent + 4, " ",
pdev->tx_desc.pool_size, pdev->tx_desc.array);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW, " ");
htt_display(pdev->htt_pdev, indent);
}
void ol_txrx_vdev_display(ol_txrx_vdev_handle vdev, int indent)
{
struct ol_txrx_peer_t *peer;
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*stxrx vdev: %pK\n", indent, " ", vdev);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*sID: %d\n", indent + 4, " ", vdev->vdev_id);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*sMAC addr: %d:%d:%d:%d:%d:%d",
indent + 4, " ",
vdev->mac_addr.raw[0], vdev->mac_addr.raw[1],
vdev->mac_addr.raw[2], vdev->mac_addr.raw[3],
vdev->mac_addr.raw[4], vdev->mac_addr.raw[5]);
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*speer list:", indent + 4, " ");
TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) {
ol_txrx_peer_display(peer, indent + 8);
}
}
void ol_txrx_peer_display(ol_txrx_peer_handle peer, int indent)
{
int i;
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*stxrx peer: %pK", indent, " ", peer);
for (i = 0; i < MAX_NUM_PEER_ID_PER_PEER; i++) {
if (peer->peer_ids[i] != HTT_INVALID_PEER) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
"%*sID: %d", indent + 4, " ",
peer->peer_ids[i]);
}
}
}
#endif /* TXRX_DEBUG_LEVEL */
/**
* ol_txrx_stats() - update ol layer stats
* @vdev_id: vdev_id
* @buffer: pointer to buffer
* @buf_len: length of the buffer
*
* Return: length of string
*/
static int
ol_txrx_stats(uint8_t vdev_id, char *buffer, unsigned int buf_len)
{
uint32_t len = 0;
struct ol_txrx_vdev_t *vdev =
(struct ol_txrx_vdev_t *)
ol_txrx_get_vdev_from_vdev_id(vdev_id);
if (!vdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: vdev is NULL", __func__);
snprintf(buffer, buf_len, "vdev not found");
return len;
}
len = scnprintf(buffer, buf_len,
"\n\nTXRX stats:\nllQueue State : %s\npause %u unpause %u\noverflow %u\nllQueue timer state : %s",
((vdev->ll_pause.is_q_paused == false) ?
"UNPAUSED" : "PAUSED"),
vdev->ll_pause.q_pause_cnt,
vdev->ll_pause.q_unpause_cnt,
vdev->ll_pause.q_overflow_cnt,
((vdev->ll_pause.is_q_timer_on == false)
? "NOT-RUNNING" : "RUNNING"));
return len;
}
#ifdef QCA_SUPPORT_TXRX_LOCAL_PEER_ID
/**
* ol_txrx_disp_peer_cached_bufq_stats() - display peer cached_bufq stats
* @peer: peer pointer
*
* Return: None
*/
static void ol_txrx_disp_peer_cached_bufq_stats(struct ol_txrx_peer_t *peer)
{
txrx_nofl_info("cached_bufq: curr %d drops %d hwm %d whatifs %d thresh %d",
peer->bufq_info.curr,
peer->bufq_info.dropped,
peer->bufq_info.high_water_mark,
peer->bufq_info.qdepth_no_thresh,
peer->bufq_info.thresh);
}
/**
* ol_txrx_disp_peer_stats() - display peer stats
* @pdev: pdev pointer
*
* Return: None
*/
static void ol_txrx_disp_peer_stats(ol_txrx_pdev_handle pdev)
{ int i;
struct ol_txrx_peer_t *peer;
struct hif_opaque_softc *osc = cds_get_context(QDF_MODULE_ID_HIF);
if (osc && hif_is_load_or_unload_in_progress(HIF_GET_SOFTC(osc)))
return;
for (i = 0; i < OL_TXRX_NUM_LOCAL_PEER_IDS; i++) {
qdf_spin_lock_bh(&pdev->peer_ref_mutex);
qdf_spin_lock_bh(&pdev->local_peer_ids.lock);
peer = pdev->local_peer_ids.map[i];
if (peer) {
ol_txrx_peer_get_ref(peer, PEER_DEBUG_ID_OL_INTERNAL);
}
qdf_spin_unlock_bh(&pdev->local_peer_ids.lock);
qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
if (peer) {
txrx_nofl_info("stats: peer 0x%pK local peer id %d",
peer, i);
ol_txrx_disp_peer_cached_bufq_stats(peer);
ol_txrx_peer_release_ref(peer,
PEER_DEBUG_ID_OL_INTERNAL);
}
}
}
#else
static void ol_txrx_disp_peer_stats(ol_txrx_pdev_handle pdev)
{
txrx_nofl_info("peer stats not supported w/o QCA_SUPPORT_TXRX_LOCAL_PEER_ID");
}
#endif
void ol_txrx_stats_display(ol_txrx_pdev_handle pdev,
enum qdf_stats_verbosity_level level)
{
u64 tx_dropped =
pdev->stats.pub.tx.dropped.download_fail.pkts
+ pdev->stats.pub.tx.dropped.target_discard.pkts
+ pdev->stats.pub.tx.dropped.no_ack.pkts
+ pdev->stats.pub.tx.dropped.target_drop.pkts
+ pdev->stats.pub.tx.dropped.others.pkts;
if (level == QDF_STATS_VERBOSITY_LEVEL_LOW) {
txrx_nofl_dbg("STATS |%u %u|TX: %lld tso %lld ok %lld drops(%u-%lld %u-%lld %u-%lld %u-%lld ?-%lld hR-%lld)|RX: %lld drops(E %lld PI %lld ME %lld) fwd(S %d F %d SF %d)|",
pdev->tx_desc.num_free,
pdev->tx_desc.pool_size,
pdev->stats.pub.tx.from_stack.pkts,
pdev->stats.pub.tx.tso.tso_pkts.pkts,
pdev->stats.pub.tx.delivered.pkts,
htt_tx_status_download_fail,
pdev->stats.pub.tx.dropped.download_fail.pkts,
htt_tx_status_discard,
pdev->stats.pub.tx.dropped.
target_discard.pkts,
htt_tx_status_no_ack,
pdev->stats.pub.tx.dropped.no_ack.pkts,
htt_tx_status_drop,
pdev->stats.pub.tx.dropped.target_drop.pkts,
pdev->stats.pub.tx.dropped.others.pkts,
pdev->stats.pub.tx.dropped.host_reject.pkts,
pdev->stats.pub.rx.delivered.pkts,
pdev->stats.pub.rx.dropped_err.pkts,
pdev->stats.pub.rx.dropped_peer_invalid.pkts,
pdev->stats.pub.rx.dropped_mic_err.pkts,
pdev->stats.pub.rx.intra_bss_fwd.
packets_stack,
pdev->stats.pub.rx.intra_bss_fwd.
packets_fwd,
pdev->stats.pub.rx.intra_bss_fwd.
packets_stack_n_fwd);
return;
}
txrx_nofl_info("TX PATH Statistics:");
txrx_nofl_info("sent %lld msdus (%lld B), host rejected %lld (%lld B), dropped %lld (%lld B)",
pdev->stats.pub.tx.from_stack.pkts,
pdev->stats.pub.tx.from_stack.bytes,
pdev->stats.pub.tx.dropped.host_reject.pkts,
pdev->stats.pub.tx.dropped.host_reject.bytes,
tx_dropped,
pdev->stats.pub.tx.dropped.download_fail.bytes
+ pdev->stats.pub.tx.dropped.target_discard.bytes
+ pdev->stats.pub.tx.dropped.target_drop.bytes
+ pdev->stats.pub.tx.dropped.no_ack.bytes);
txrx_nofl_info("successfully delivered: %lld (%lld B), download fail: %lld (%lld B), target discard: %lld (%lld B), no ack: %lld (%lld B),target drop: %lld (%lld B), others: %lld (%lld B)",
pdev->stats.pub.tx.delivered.pkts,
pdev->stats.pub.tx.delivered.bytes,
pdev->stats.pub.tx.dropped.download_fail.pkts,
pdev->stats.pub.tx.dropped.download_fail.bytes,
pdev->stats.pub.tx.dropped.target_discard.pkts,
pdev->stats.pub.tx.dropped.target_discard.bytes,
pdev->stats.pub.tx.dropped.no_ack.pkts,
pdev->stats.pub.tx.dropped.no_ack.bytes,
pdev->stats.pub.tx.dropped.target_drop.pkts,
pdev->stats.pub.tx.dropped.target_drop.bytes,
pdev->stats.pub.tx.dropped.others.pkts,
pdev->stats.pub.tx.dropped.others.bytes);
txrx_nofl_info("Tx completions per HTT message:\n"
"Single Packet %d\n"
" 2-10 Packets %d\n"
"11-20 Packets %d\n"
"21-30 Packets %d\n"
"31-40 Packets %d\n"
"41-50 Packets %d\n"
"51-60 Packets %d\n"
" 60+ Packets %d\n",
pdev->stats.pub.tx.comp_histogram.pkts_1,
pdev->stats.pub.tx.comp_histogram.pkts_2_10,
pdev->stats.pub.tx.comp_histogram.pkts_11_20,
pdev->stats.pub.tx.comp_histogram.pkts_21_30,
pdev->stats.pub.tx.comp_histogram.pkts_31_40,
pdev->stats.pub.tx.comp_histogram.pkts_41_50,
pdev->stats.pub.tx.comp_histogram.pkts_51_60,
pdev->stats.pub.tx.comp_histogram.pkts_61_plus);
txrx_nofl_info("RX PATH Statistics:");
txrx_nofl_info("%lld ppdus, %lld mpdus, %lld msdus, %lld bytes\n"
"dropped: err %lld (%lld B), peer_invalid %lld (%lld B), mic_err %lld (%lld B)\n"
"msdus with frag_ind: %d msdus with offload_ind: %d",
pdev->stats.priv.rx.normal.ppdus,
pdev->stats.priv.rx.normal.mpdus,
pdev->stats.pub.rx.delivered.pkts,
pdev->stats.pub.rx.delivered.bytes,
pdev->stats.pub.rx.dropped_err.pkts,
pdev->stats.pub.rx.dropped_err.bytes,
pdev->stats.pub.rx.dropped_peer_invalid.pkts,
pdev->stats.pub.rx.dropped_peer_invalid.bytes,
pdev->stats.pub.rx.dropped_mic_err.pkts,
pdev->stats.pub.rx.dropped_mic_err.bytes,
pdev->stats.pub.rx.msdus_with_frag_ind,
pdev->stats.pub.rx.msdus_with_offload_ind);
txrx_nofl_info(" fwd to stack %d, fwd to fw %d, fwd to stack & fw %d\n",
pdev->stats.pub.rx.intra_bss_fwd.packets_stack,
pdev->stats.pub.rx.intra_bss_fwd.packets_fwd,
pdev->stats.pub.rx.intra_bss_fwd.packets_stack_n_fwd);
txrx_nofl_info("packets per HTT message:\n"
"Single Packet %d\n"
" 2-10 Packets %d\n"
"11-20 Packets %d\n"
"21-30 Packets %d\n"
"31-40 Packets %d\n"
"41-50 Packets %d\n"
"51-60 Packets %d\n"
" 60+ Packets %d\n",
pdev->stats.pub.rx.rx_ind_histogram.pkts_1,
pdev->stats.pub.rx.rx_ind_histogram.pkts_2_10,
pdev->stats.pub.rx.rx_ind_histogram.pkts_11_20,
pdev->stats.pub.rx.rx_ind_histogram.pkts_21_30,
pdev->stats.pub.rx.rx_ind_histogram.pkts_31_40,
pdev->stats.pub.rx.rx_ind_histogram.pkts_41_50,
pdev->stats.pub.rx.rx_ind_histogram.pkts_51_60,
pdev->stats.pub.rx.rx_ind_histogram.pkts_61_plus);
ol_txrx_disp_peer_stats(pdev);
}
void ol_txrx_stats_clear(ol_txrx_pdev_handle pdev)
{
qdf_mem_zero(&pdev->stats, sizeof(pdev->stats));
}
#if defined(ENABLE_TXRX_PROT_ANALYZE)
void ol_txrx_prot_ans_display(ol_txrx_pdev_handle pdev)
{
ol_txrx_prot_an_display(pdev->prot_an_tx_sent);
ol_txrx_prot_an_display(pdev->prot_an_rx_sent);
}
#endif /* ENABLE_TXRX_PROT_ANALYZE */
#ifdef QCA_SUPPORT_PEER_DATA_RX_RSSI
int16_t ol_txrx_peer_rssi(ol_txrx_peer_handle peer)
{
return (peer->rssi_dbm == HTT_RSSI_INVALID) ?
OL_TXRX_RSSI_INVALID : peer->rssi_dbm;
}
#endif /* #ifdef QCA_SUPPORT_PEER_DATA_RX_RSSI */
#ifdef QCA_ENABLE_OL_TXRX_PEER_STATS
A_STATUS
ol_txrx_peer_stats_copy(ol_txrx_pdev_handle pdev,
ol_txrx_peer_handle peer, ol_txrx_peer_stats_t *stats)
{
qdf_assert(pdev && peer && stats);
qdf_spin_lock_bh(&pdev->peer_stat_mutex);
qdf_mem_copy(stats, &peer->stats, sizeof(*stats));
qdf_spin_unlock_bh(&pdev->peer_stat_mutex);
return A_OK;
}
#endif /* QCA_ENABLE_OL_TXRX_PEER_STATS */
static void ol_vdev_rx_set_intrabss_fwd(struct cdp_vdev *pvdev, bool val)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
if (!vdev)
return;
vdev->disable_intrabss_fwd = val;
}
/**
* ol_txrx_update_mac_id() - update mac_id for vdev
* @vdev_id: vdev id
* @mac_id: mac id
*
* Return: none
*/
static void ol_txrx_update_mac_id(uint8_t vdev_id, uint8_t mac_id)
{
struct ol_txrx_vdev_t *vdev =
(struct ol_txrx_vdev_t *)
ol_txrx_get_vdev_from_vdev_id(vdev_id);
if (!vdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: Invalid vdev_id %d", __func__, vdev_id);
return;
}
vdev->mac_id = mac_id;
}
/**
* ol_txrx_get_tx_ack_count() - get tx ack count
* @pdev: pdev reference
* @vdev_id: vdev_id
*
* Return: tx ack count
*/
static uint32_t ol_txrx_get_tx_ack_stats(struct cdp_pdev *pdev,
uint8_t vdev_id)
{
struct ol_txrx_vdev_t *vdev =
(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);
if (!vdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: Invalid vdev_id %d", __func__, vdev_id);
return 0;
}
return vdev->txrx_stats.txack_success;
}
/**
* ol_txrx_display_stats() - Display OL TXRX display stats
* @value: Module id for which stats needs to be displayed
*
* Return: status
*/
static QDF_STATUS
ol_txrx_display_stats(void *soc, uint16_t value,
enum qdf_stats_verbosity_level verb_level)
{
ol_txrx_pdev_handle pdev;
QDF_STATUS status = QDF_STATUS_SUCCESS;
pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: pdev is NULL", __func__);
return QDF_STATUS_E_NULL_VALUE;
}
switch (value) {
case CDP_TXRX_PATH_STATS:
ol_txrx_stats_display(pdev, verb_level);
break;
case CDP_TXRX_TSO_STATS:
ol_txrx_stats_display_tso(pdev);
break;
case CDP_DUMP_TX_FLOW_POOL_INFO:
if (verb_level == QDF_STATS_VERBOSITY_LEVEL_LOW)
ol_tx_dump_flow_pool_info_compact((void *)pdev);
else
ol_tx_dump_flow_pool_info((void *)pdev);
break;
case CDP_TXRX_DESC_STATS:
qdf_nbuf_tx_desc_count_display();
break;
case CDP_WLAN_RX_BUF_DEBUG_STATS:
htt_display_rx_buf_debug(pdev->htt_pdev);
break;
#ifdef CONFIG_HL_SUPPORT
case CDP_SCHEDULER_STATS:
ol_tx_sched_cur_state_display(pdev);
ol_tx_sched_stats_display(pdev);
break;
case CDP_TX_QUEUE_STATS:
ol_tx_queue_log_display(pdev);
break;
#ifdef FEATURE_HL_GROUP_CREDIT_FLOW_CONTROL
case CDP_CREDIT_STATS:
ol_tx_dump_group_credit_stats(pdev);
break;
#endif
#ifdef DEBUG_HL_LOGGING
case CDP_BUNDLE_STATS:
htt_dump_bundle_stats(pdev->htt_pdev);
break;
#endif
#endif
default:
status = QDF_STATUS_E_INVAL;
break;
}
return status;
}
/**
* ol_txrx_clear_stats() - Clear OL TXRX stats
* @soc - ol soc handle
* @value - Module id for which stats needs to be cleared
*
* Return: 0 - success/ non-zero failure
*/
static QDF_STATUS ol_txrx_clear_stats(struct cdp_soc *soc,
uint8_t value)
{
ol_txrx_pdev_handle pdev;
QDF_STATUS status = QDF_STATUS_SUCCESS;
pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: pdev is NULL", __func__);
return QDF_STATUS_E_INVAL;
}
switch (value) {
case CDP_TXRX_PATH_STATS:
ol_txrx_stats_clear(pdev);
break;
case CDP_TXRX_TSO_STATS:
ol_txrx_tso_stats_clear(pdev);
break;
case CDP_DUMP_TX_FLOW_POOL_INFO:
ol_tx_clear_flow_pool_stats();
break;
case CDP_TXRX_DESC_STATS:
qdf_nbuf_tx_desc_count_clear();
break;
#ifdef CONFIG_HL_SUPPORT
case CDP_SCHEDULER_STATS:
ol_tx_sched_stats_clear(pdev);
break;
case CDP_TX_QUEUE_STATS:
ol_tx_queue_log_clear(pdev);
break;
#ifdef FEATURE_HL_GROUP_CREDIT_FLOW_CONTROL
case CDP_CREDIT_STATS:
ol_tx_clear_group_credit_stats(pdev);
break;
#endif
case CDP_BUNDLE_STATS:
htt_clear_bundle_stats(pdev->htt_pdev);
break;
#endif
default:
status = QDF_STATUS_E_INVAL;
break;
}
return status;
}
/**
* ol_txrx_drop_nbuf_list() - drop an nbuf list
* @buf_list: buffer list to be dropepd
*
* Return: int (number of bufs dropped)
*/
static inline int ol_txrx_drop_nbuf_list(qdf_nbuf_t buf_list)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
int num_dropped = 0;
qdf_nbuf_t buf, next_buf;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return 0;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return 0;
}
buf = buf_list;
while (buf) {
QDF_NBUF_CB_RX_PEER_CACHED_FRM(buf) = 1;
next_buf = qdf_nbuf_queue_next(buf);
if (pdev)
TXRX_STATS_MSDU_INCR(pdev,
rx.dropped_peer_invalid, buf);
qdf_nbuf_free(buf);
buf = next_buf;
num_dropped++;
}
return num_dropped;
}
/**
* ol_rx_data_handler() - data rx handler
* @pdev: dev handle
* @buf_list: buffer list
* @staid: Station id
*
* Return: None
*/
static void ol_rx_data_handler(struct ol_txrx_pdev_t *pdev,
qdf_nbuf_t buf_list, uint16_t staid)
{
void *osif_dev;
uint8_t drop_count = 0;
qdf_nbuf_t buf, next_buf;
QDF_STATUS ret;
ol_txrx_rx_fp data_rx = NULL;
struct ol_txrx_peer_t *peer;
if (qdf_unlikely(!pdev))
goto free_buf;
/* Do not use peer directly. Derive peer from staid to
* make sure that peer is valid.
*/
peer = ol_txrx_peer_get_ref_by_local_id((struct cdp_pdev *)pdev,
staid, PEER_DEBUG_ID_OL_RX_THREAD);
if (!peer)
goto free_buf;
qdf_spin_lock_bh(&peer->peer_info_lock);
if (qdf_unlikely(!(peer->state >= OL_TXRX_PEER_STATE_CONN) ||
!peer->vdev->rx)) {
qdf_spin_unlock_bh(&peer->peer_info_lock);
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_RX_THREAD);
goto free_buf;
}
data_rx = peer->vdev->rx;
osif_dev = peer->vdev->osif_dev;
qdf_spin_unlock_bh(&peer->peer_info_lock);
qdf_spin_lock_bh(&peer->bufq_info.bufq_lock);
if (!list_empty(&peer->bufq_info.cached_bufq)) {
qdf_spin_unlock_bh(&peer->bufq_info.bufq_lock);
/* Flush the cached frames to HDD before passing new rx frame */
ol_txrx_flush_rx_frames(peer, 0);
} else
qdf_spin_unlock_bh(&peer->bufq_info.bufq_lock);
ol_txrx_peer_release_ref(peer, PEER_DEBUG_ID_OL_RX_THREAD);
buf = buf_list;
while (buf) {
next_buf = qdf_nbuf_queue_next(buf);
qdf_nbuf_set_next(buf, NULL); /* Add NULL terminator */
ret = data_rx(osif_dev, buf);
if (ret != QDF_STATUS_SUCCESS) {
ol_txrx_err("Frame Rx to HDD failed");
if (pdev)
TXRX_STATS_MSDU_INCR(pdev, rx.dropped_err, buf);
qdf_nbuf_free(buf);
}
buf = next_buf;
}
return;
free_buf:
drop_count = ol_txrx_drop_nbuf_list(buf_list);
ol_txrx_warn("Dropped frames %u", drop_count);
}
/**
* ol_rx_data_cb() - data rx callback
* @context: dev handle
* @buf_list: buffer list
* @staid: Station id
*
* Return: None
*/
static inline void
ol_rx_data_cb(void *context, qdf_nbuf_t buf_list, uint16_t staid)
{
struct ol_txrx_pdev_t *pdev = context;
ol_rx_data_handler(pdev, buf_list, staid);
}
/* print for every 16th packet */
#define OL_TXRX_PRINT_RATE_LIMIT_THRESH 0x0f
struct ol_rx_cached_buf *cache_buf;
/** helper function to drop packets
* Note: caller must hold the cached buq lock before invoking
* this function. Also, it assumes that the pointers passed in
* are valid (non-NULL)
*/
static inline void ol_txrx_drop_frames(
struct ol_txrx_cached_bufq_t *bufqi,
qdf_nbuf_t rx_buf_list)
{
uint32_t dropped = ol_txrx_drop_nbuf_list(rx_buf_list);
bufqi->dropped += dropped;
bufqi->qdepth_no_thresh += dropped;
if (bufqi->qdepth_no_thresh > bufqi->high_water_mark)
bufqi->high_water_mark = bufqi->qdepth_no_thresh;
}
static QDF_STATUS ol_txrx_enqueue_rx_frames(
struct ol_txrx_peer_t *peer,
struct ol_txrx_cached_bufq_t *bufqi,
qdf_nbuf_t rx_buf_list)
{
struct ol_rx_cached_buf *cache_buf;
qdf_nbuf_t buf, next_buf;
static uint32_t count;
if ((count++ & OL_TXRX_PRINT_RATE_LIMIT_THRESH) == 0)
ol_txrx_info_high(
"Data on the peer before it is registered bufq->curr %d bufq->drops %d",
bufqi->curr, bufqi->dropped);
qdf_spin_lock_bh(&bufqi->bufq_lock);
if (bufqi->curr >= bufqi->thresh) {
ol_txrx_drop_frames(bufqi, rx_buf_list);
qdf_spin_unlock_bh(&bufqi->bufq_lock);
return QDF_STATUS_E_FAULT;
}
qdf_spin_unlock_bh(&bufqi->bufq_lock);
buf = rx_buf_list;
while (buf) {
QDF_NBUF_CB_RX_PEER_CACHED_FRM(buf) = 1;
next_buf = qdf_nbuf_queue_next(buf);
cache_buf = qdf_mem_malloc(sizeof(*cache_buf));
if (!cache_buf) {
qdf_nbuf_free(buf);
} else {
/* Add NULL terminator */
qdf_nbuf_set_next(buf, NULL);
cache_buf->buf = buf;
if (peer && peer->valid) {
qdf_spin_lock_bh(&bufqi->bufq_lock);
list_add_tail(&cache_buf->list,
&bufqi->cached_bufq);
bufqi->curr++;
qdf_spin_unlock_bh(&bufqi->bufq_lock);
} else {
qdf_mem_free(cache_buf);
rx_buf_list = buf;
qdf_nbuf_set_next(rx_buf_list, next_buf);
qdf_spin_lock_bh(&bufqi->bufq_lock);
ol_txrx_drop_frames(bufqi, rx_buf_list);
qdf_spin_unlock_bh(&bufqi->bufq_lock);
return QDF_STATUS_E_FAULT;
}
}
buf = next_buf;
}
return QDF_STATUS_SUCCESS;
}
/**
* ol_rx_data_process() - process rx frame
* @peer: peer
* @rx_buf_list: rx buffer list
*
* Return: None
*/
void ol_rx_data_process(struct ol_txrx_peer_t *peer,
qdf_nbuf_t rx_buf_list)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
/*
* Firmware data path active response will use shim RX thread
* T2H MSG running on SIRQ context,
* IPA kernel module API should not be called on SIRQ CTXT
*/
ol_txrx_rx_fp data_rx = NULL;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
goto drop_rx_buf;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if ((!peer) || (!pdev)) {
ol_txrx_err("peer/pdev is NULL");
goto drop_rx_buf;
}
qdf_assert(peer->vdev);
qdf_spin_lock_bh(&peer->peer_info_lock);
if (peer->state >= OL_TXRX_PEER_STATE_CONN)
data_rx = peer->vdev->rx;
qdf_spin_unlock_bh(&peer->peer_info_lock);
/*
* If there is a data frame from peer before the peer is
* registered for data service, enqueue them on to pending queue
* which will be flushed to HDD once that station is registered.
*/
if (!data_rx) {
if (ol_txrx_enqueue_rx_frames(peer, &peer->bufq_info,
rx_buf_list)
!= QDF_STATUS_SUCCESS)
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_HIGH,
"%s: failed to enqueue rx frm to cached_bufq",
__func__);
} else {
#ifdef QCA_CONFIG_SMP
/*
* If the kernel is SMP, schedule rx thread to
* better use multicores.
*/
if (!ol_cfg_is_rx_thread_enabled(pdev->ctrl_pdev)) {
ol_rx_data_handler(pdev, rx_buf_list, peer->local_id);
} else {
p_cds_sched_context sched_ctx =
get_cds_sched_ctxt();
struct cds_ol_rx_pkt *pkt;
if (unlikely(!sched_ctx))
goto drop_rx_buf;
pkt = cds_alloc_ol_rx_pkt(sched_ctx);
if (!pkt)
goto drop_rx_buf;
pkt->callback = ol_rx_data_cb;
pkt->context = pdev;
pkt->Rxpkt = rx_buf_list;
pkt->staId = peer->local_id;
cds_indicate_rxpkt(sched_ctx, pkt);
}
#else /* QCA_CONFIG_SMP */
ol_rx_data_handler(pdev, rx_buf_list, peer->local_id);
#endif /* QCA_CONFIG_SMP */
}
return;
drop_rx_buf:
ol_txrx_drop_nbuf_list(rx_buf_list);
}
/**
* ol_txrx_register_peer() - register peer
* @sta_desc: sta descriptor
*
* Return: QDF Status
*/
static QDF_STATUS ol_txrx_register_peer(struct ol_txrx_desc_type *sta_desc)
{
struct ol_txrx_peer_t *peer;
struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX);
union ol_txrx_peer_update_param_t param;
struct privacy_exemption privacy_filter;
uint8_t peer_id;
if (!pdev) {
ol_txrx_err("Pdev is NULL");
return QDF_STATUS_E_INVAL;
}
peer = ol_txrx_find_peer_by_addr((struct cdp_pdev *)pdev,
sta_desc->peer_addr.bytes,
&peer_id);
if (!peer)
return QDF_STATUS_E_FAULT;
qdf_spin_lock_bh(&peer->peer_info_lock);
peer->state = OL_TXRX_PEER_STATE_CONN;
qdf_spin_unlock_bh(&peer->peer_info_lock);
param.qos_capable = sta_desc->is_qos_enabled;
ol_txrx_peer_update(peer->vdev, peer->mac_addr.raw, &param,
ol_txrx_peer_update_qos_capable);
if (sta_desc->is_wapi_supported) {
/*Privacy filter to accept unencrypted WAI frames */
privacy_filter.ether_type = ETHERTYPE_WAI;
privacy_filter.filter_type = PRIVACY_FILTER_ALWAYS;
privacy_filter.packet_type = PRIVACY_FILTER_PACKET_BOTH;
ol_txrx_set_privacy_filters(peer->vdev, &privacy_filter, 1);
}
ol_txrx_flush_rx_frames(peer, 0);
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_register_ocb_peer - Function to register the OCB peer
* @mac_addr: MAC address of the self peer
* @peer_id: Pointer to the peer ID
*
* Return: QDF_STATUS_SUCCESS on success, QDF_STATUS_E_FAILURE on failure
*/
static QDF_STATUS ol_txrx_register_ocb_peer(uint8_t *mac_addr,
uint8_t *peer_id)
{
ol_txrx_pdev_handle pdev;
ol_txrx_peer_handle peer;
pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
ol_txrx_err("Unable to find pdev!");
return QDF_STATUS_E_FAILURE;
}
peer = ol_txrx_find_peer_by_addr((struct cdp_pdev *)pdev,
mac_addr, peer_id);
if (!peer) {
ol_txrx_err("Unable to find OCB peer!");
return QDF_STATUS_E_FAILURE;
}
ol_txrx_set_ocb_peer(pdev, peer);
/* Set peer state to connected */
ol_txrx_peer_state_update((struct cdp_pdev *)pdev, peer->mac_addr.raw,
OL_TXRX_PEER_STATE_AUTH);
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_set_ocb_peer - Function to store the OCB peer
* @pdev: Handle to the HTT instance
* @peer: Pointer to the peer
*/
void ol_txrx_set_ocb_peer(struct ol_txrx_pdev_t *pdev,
struct ol_txrx_peer_t *peer)
{
if (!pdev)
return;
pdev->ocb_peer = peer;
pdev->ocb_peer_valid = (NULL != peer);
}
/**
* ol_txrx_get_ocb_peer - Function to retrieve the OCB peer
* @pdev: Handle to the HTT instance
* @peer: Pointer to the returned peer
*
* Return: true if the peer is valid, false if not
*/
bool ol_txrx_get_ocb_peer(struct ol_txrx_pdev_t *pdev,
struct ol_txrx_peer_t **peer)
{
int rc;
if ((!pdev) || (!peer)) {
rc = false;
goto exit;
}
if (pdev->ocb_peer_valid) {
*peer = pdev->ocb_peer;
rc = true;
} else {
rc = false;
}
exit:
return rc;
}
/**
* ol_txrx_register_pause_cb() - register pause callback
* @pause_cb: pause callback
*
* Return: QDF status
*/
static QDF_STATUS ol_txrx_register_pause_cb(struct cdp_soc_t *soc,
tx_pause_callback pause_cb)
{
struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev || !pause_cb) {
ol_txrx_err("pdev or pause_cb is NULL");
return QDF_STATUS_E_INVAL;
}
pdev->pause_cb = pause_cb;
return QDF_STATUS_SUCCESS;
}
#ifdef RECEIVE_OFFLOAD
/**
* ol_txrx_offld_flush_handler() - offld flush handler
* @context: dev handle
* @rxpkt: rx data
* @staid: station id
*
* This function handles an offld flush indication.
* If the rx thread is enabled, it will be invoked by the rx
* thread else it will be called in the tasklet context
*
* Return: none
*/
static void ol_txrx_offld_flush_handler(void *context,
qdf_nbuf_t rxpkt,
uint16_t staid)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("Invalid soc context");
qdf_assert(0);
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (qdf_unlikely(!pdev)) {
ol_txrx_err("Invalid pdev context");
qdf_assert(0);
return;
}
if (pdev->offld_flush_cb)
pdev->offld_flush_cb(context);
else
ol_txrx_err("offld_flush_cb NULL");
}
/**
* ol_txrx_offld_flush() - offld flush callback
* @data: opaque data pointer
*
* This is the callback registered with CE to trigger
* an offld flush
*
* Return: none
*/
static void ol_txrx_offld_flush(void *data)
{
p_cds_sched_context sched_ctx = get_cds_sched_ctxt();
struct cds_ol_rx_pkt *pkt;
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!sched_ctx))
return;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (qdf_unlikely(!pdev)) {
ol_txrx_err("TXRX module context is NULL");
return;
}
if (!ol_cfg_is_rx_thread_enabled(pdev->ctrl_pdev)) {
ol_txrx_offld_flush_handler(data, NULL, 0);
} else {
pkt = cds_alloc_ol_rx_pkt(sched_ctx);
if (qdf_unlikely(!pkt))
return;
pkt->callback = ol_txrx_offld_flush_handler;
pkt->context = data;
pkt->Rxpkt = NULL;
pkt->staId = 0;
cds_indicate_rxpkt(sched_ctx, pkt);
}
}
/**
* ol_register_offld_flush_cb() - register the offld flush callback
* @offld_flush_cb: flush callback function
* @offld_init_cb: Allocate and initialize offld data structure.
*
* Store the offld flush callback provided and in turn
* register OL's offld flush handler with CE
*
* Return: none
*/
static void ol_register_offld_flush_cb(void (offld_flush_cb)(void *))
{
struct hif_opaque_softc *hif_device;
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc NULL!");
TXRX_ASSERT2(0);
goto out;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
ol_txrx_err("pdev NULL!");
TXRX_ASSERT2(0);
goto out;
}
if (pdev->offld_flush_cb) {
ol_txrx_info("offld already initialised");
if (pdev->offld_flush_cb != offld_flush_cb) {
ol_txrx_err(
"offld_flush_cb is differ to previously registered callback")
TXRX_ASSERT2(0);
goto out;
}
goto out;
}
pdev->offld_flush_cb = offld_flush_cb;
hif_device = cds_get_context(QDF_MODULE_ID_HIF);
if (qdf_unlikely(!hif_device)) {
ol_txrx_err("hif_device NULL!");
qdf_assert(0);
goto out;
}
hif_offld_flush_cb_register(hif_device, ol_txrx_offld_flush);
out:
return;
}
/**
* ol_deregister_offld_flush_cb() - deregister the offld flush callback
*
* Remove the offld flush callback provided and in turn
* deregister OL's offld flush handler with CE
*
* Return: none
*/
static void ol_deregister_offld_flush_cb(void)
{
struct hif_opaque_softc *hif_device;
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
ol_txrx_err("pdev NULL!");
return;
}
hif_device = cds_get_context(QDF_MODULE_ID_HIF);
if (qdf_unlikely(!hif_device)) {
ol_txrx_err("hif_device NULL!");
qdf_assert(0);
return;
}
hif_offld_flush_cb_deregister(hif_device);
pdev->offld_flush_cb = NULL;
}
#endif /* RECEIVE_OFFLOAD */
/**
* ol_register_data_stall_detect_cb() - register data stall callback
* @data_stall_detect_callback: data stall callback function
*
*
* Return: QDF_STATUS Enumeration
*/
static QDF_STATUS ol_register_data_stall_detect_cb(
data_stall_detect_cb data_stall_detect_callback)
{
struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
ol_txrx_err("pdev NULL!");
return QDF_STATUS_E_INVAL;
}
pdev->data_stall_detect_callback = data_stall_detect_callback;
return QDF_STATUS_SUCCESS;
}
/**
* ol_deregister_data_stall_detect_cb() - de-register data stall callback
* @data_stall_detect_callback: data stall callback function
*
*
* Return: QDF_STATUS Enumeration
*/
static QDF_STATUS ol_deregister_data_stall_detect_cb(
data_stall_detect_cb data_stall_detect_callback)
{
struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
ol_txrx_err("pdev NULL!");
return QDF_STATUS_E_INVAL;
}
pdev->data_stall_detect_callback = NULL;
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_post_data_stall_event() - post data stall event
* @indicator: Module triggering data stall
* @data_stall_type: data stall event type
* @pdev_id: pdev id
* @vdev_id_bitmap: vdev id bitmap
* @recovery_type: data stall recovery type
*
* Return: None
*/
static void ol_txrx_post_data_stall_event(
enum data_stall_log_event_indicator indicator,
enum data_stall_log_event_type data_stall_type,
uint32_t pdev_id, uint32_t vdev_id_bitmap,
enum data_stall_log_recovery_type recovery_type)
{
struct scheduler_msg msg = {0};
QDF_STATUS status;
struct data_stall_event_info *data_stall_info;
ol_txrx_pdev_handle pdev;
pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: pdev is NULL.", __func__);
return;
}
data_stall_info = qdf_mem_malloc(sizeof(*data_stall_info));
if (!data_stall_info)
return;
data_stall_info->indicator = indicator;
data_stall_info->data_stall_type = data_stall_type;
data_stall_info->vdev_id_bitmap = vdev_id_bitmap;
data_stall_info->pdev_id = pdev_id;
data_stall_info->recovery_type = recovery_type;
if (data_stall_info->data_stall_type ==
DATA_STALL_LOG_FW_RX_REFILL_FAILED)
htt_log_rx_ring_info(pdev->htt_pdev);
sys_build_message_header(SYS_MSG_ID_DATA_STALL_MSG, &msg);
/* Save callback and data */
msg.callback = pdev->data_stall_detect_callback;
msg.bodyptr = data_stall_info;
msg.bodyval = 0;
status = scheduler_post_message(QDF_MODULE_ID_TXRX,
QDF_MODULE_ID_HDD,
QDF_MODULE_ID_SYS, &msg);
if (status != QDF_STATUS_SUCCESS)
qdf_mem_free(data_stall_info);
}
void
ol_txrx_dump_pkt(qdf_nbuf_t nbuf, uint32_t nbuf_paddr, int len)
{
qdf_print(" Pkt: VA 0x%pK PA 0x%llx len %d\n",
qdf_nbuf_data(nbuf), (unsigned long long int)nbuf_paddr, len);
print_hex_dump(KERN_DEBUG, "Pkt: ", DUMP_PREFIX_ADDRESS, 16, 4,
qdf_nbuf_data(nbuf), len, true);
}
struct cdp_vdev *ol_txrx_get_vdev_from_vdev_id(uint8_t vdev_id)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_vdev_handle vdev = NULL;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return NULL;
}
vdev = ol_txrx_get_vdev_from_soc_vdev_id(soc, vdev_id);
return ol_txrx_vdev_t_to_cdp_vdev(vdev);
}
struct ol_txrx_vdev_t *ol_txrx_get_vdev_from_soc_vdev_id(
struct ol_txrx_soc_t *soc, uint8_t vdev_id)
{
ol_txrx_pdev_handle pdev;
ol_txrx_vdev_handle vdev = NULL;
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (qdf_unlikely(!pdev))
return NULL;
TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) {
if (vdev->vdev_id == vdev_id)
break;
}
return vdev;
}
/**
* ol_txrx_get_mon_vdev_from_pdev() - get monitor mode vdev from pdev
* @ppdev: the physical device the virtual device belongs to
*
* Return: vdev handle
* NULL if not found.
*/
struct cdp_vdev *ol_txrx_get_mon_vdev_from_pdev(struct cdp_pdev *ppdev)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
if (qdf_unlikely(!pdev))
return NULL;
return (struct cdp_vdev *)pdev->monitor_vdev;
}
/**
* ol_txrx_set_wisa_mode() - set wisa mode
* @vdev: vdev handle
* @enable: enable flag
*
* Return: QDF STATUS
*/
static QDF_STATUS ol_txrx_set_wisa_mode(struct cdp_vdev *pvdev, bool enable)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
if (!vdev)
return QDF_STATUS_E_INVAL;
vdev->is_wisa_mode_enable = enable;
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_get_vdev_id() - get interface id from interface context
* @pvdev: vdev handle
*
* Return: virtual interface id
*/
static uint16_t ol_txrx_get_vdev_id(struct cdp_vdev *pvdev)
{
struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)pvdev;
return vdev->vdev_id;
}
/**
* ol_txrx_soc_attach_target() - attach soc target
* @soc: soc handle
*
* MCL legacy OL do nothing here
*
* Return: 0
*/
static QDF_STATUS ol_txrx_soc_attach_target(ol_txrx_soc_handle soc)
{
/* MCL legacy OL do nothing here */
return QDF_STATUS_SUCCESS;
}
/**
* ol_txrx_soc_detach() - detach soc target
* @soc: soc handle
*
* MCL legacy OL do nothing here
*
* Return: noe
*/
static void ol_txrx_soc_detach(void *soc)
{
qdf_mem_free(soc);
}
/**
* ol_txrx_pkt_log_con_service() - connect packet log service
* @ppdev: physical device handle
* @scn: device context
*
* Return: noe
*/
#ifdef REMOVE_PKT_LOG
static void ol_txrx_pkt_log_con_service(struct cdp_pdev *ppdev, void *scn)
{
}
#else
static void ol_txrx_pkt_log_con_service(struct cdp_pdev *ppdev, void *scn)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
htt_pkt_log_init((struct cdp_pdev *)pdev, scn);
pktlog_htc_attach();
}
#endif
/* OL wrapper functions for CDP abstraction */
/**
* ol_txrx_wrapper_flush_rx_frames() - flush rx frames on the queue
* @peer: peer handle
* @drop: rx packets drop or deliver
*
* Return: none
*/
static void ol_txrx_wrapper_flush_rx_frames(void *peer, bool drop)
{
ol_txrx_flush_rx_frames((ol_txrx_peer_handle)peer, drop);
}
/**
* ol_txrx_wrapper_get_vdev_from_vdev_id() - get vdev instance from vdev id
* @ppdev: pdev handle
* @vdev_id: interface id
*
* Return: virtual interface instance
*/
static
struct cdp_vdev *ol_txrx_wrapper_get_vdev_from_vdev_id(struct cdp_pdev *ppdev,
uint8_t vdev_id)
{
return ol_txrx_get_vdev_from_vdev_id(vdev_id);
}
/**
* ol_txrx_pdev_set_ctrl_pdev() - set ctrl pdev handle in txrx pdev
* @txrx_pdev: txrx pdev handle
* @ctrl_pdev: UMAC ctrl pdev handle
*
* Return: void
*/
static void
ol_txrx_pdev_set_ctrl_pdev(struct cdp_pdev *txrx_pdev,
struct cdp_ctrl_objmgr_pdev *ctrl_pdev)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)txrx_pdev;
pdev->control_pdev = ctrl_pdev;
}
/**
* ol_txrx_wrapper_register_peer() - register peer
* @pdev: pdev handle
* @sta_desc: peer description
*
* Return: QDF STATUS
*/
static QDF_STATUS ol_txrx_wrapper_register_peer(struct cdp_pdev *pdev,
struct ol_txrx_desc_type *sta_desc)
{
return ol_txrx_register_peer(sta_desc);
}
/**
* ol_txrx_wrapper_peer_find_by_local_id() - Find a txrx peer handle
* @pdev - the data physical device object
* @local_peer_id - the ID txrx assigned locally to the peer in question
*
* The control SW typically uses the txrx peer handle to refer to the peer.
* In unusual circumstances, if it is infeasible for the control SW maintain
* the txrx peer handle but it can maintain a small integer local peer ID,
* this function allows the peer handled to be retrieved, based on the local
* peer ID.
*
* @return handle to the txrx peer object
*/
static void *
ol_txrx_wrapper_peer_find_by_local_id(struct cdp_pdev *pdev,
uint8_t local_peer_id)
{
return (void *)ol_txrx_peer_find_by_local_id(
pdev, local_peer_id);
}
/**
* ol_txrx_wrapper_cfg_is_high_latency() - device is high or low latency device
* @pdev: pdev handle
*
* Return: 1 high latency bus
* 0 low latency bus
*/
static int ol_txrx_wrapper_cfg_is_high_latency(struct cdp_cfg *cfg_pdev)
{
return ol_cfg_is_high_latency(cfg_pdev);
}
/**
* ol_txrx_wrapper_peer_state_update() - specify the peer's authentication state
* @data_peer - which peer has changed its state
* @state - the new state of the peer
*
* Specify the peer's authentication state (none, connected, authenticated)
* to allow the data SW to determine whether to filter out invalid data frames.
* (In the "connected" state, where security is enabled, but authentication
* has not completed, tx and rx data frames other than EAPOL or WAPI should
* be discarded.)
* This function is only relevant for systems in which the tx and rx filtering
* are done in the host rather than in the target.
*
* Return: QDF Status
*/
static QDF_STATUS ol_txrx_wrapper_peer_state_update(struct cdp_pdev *pdev,
uint8_t *peer_mac, enum ol_txrx_peer_state state)
{
return ol_txrx_peer_state_update(pdev,
peer_mac, state);
}
/**
* ol_txrx_wrapper_find_peer_by_addr() - find peer instance by address
* @pdev: pdev handle
* @peer_addr: peer address want to find
* @peer_id: peer id
*
* Return: peer instance pointer
*/
static void *ol_txrx_wrapper_find_peer_by_addr(struct cdp_pdev *pdev,
uint8_t *peer_addr, uint8_t *peer_id)
{
return ol_txrx_find_peer_by_addr(pdev,
peer_addr, peer_id);
}
/**
* ol_txrx_wrapper_peer_get_ref_by_addr() - get peer reference by address
* @pdev: pdev handle
* @peer_addr: peer address we want to find
* @peer_id: peer id
* @debug_id: peer debug id for tracking
*
* Return: peer instance pointer
*/
static void *
ol_txrx_wrapper_peer_get_ref_by_addr(struct cdp_pdev *pdev,
u8 *peer_addr, uint8_t *peer_id,
enum peer_debug_id_type debug_id)
{
return ol_txrx_peer_get_ref_by_addr((ol_txrx_pdev_handle)pdev,
peer_addr, peer_id, debug_id);
}
/**
* ol_txrx_wrapper_peer_release_ref() - release peer reference
* @peer: peer handle
* @debug_id: peer debug id for tracking
*
* Release peer ref acquired by peer get ref api
*
* Return: void
*/
static void ol_txrx_wrapper_peer_release_ref(void *peer,
enum peer_debug_id_type debug_id)
{
ol_txrx_peer_release_ref(peer, debug_id);
}
/**
* ol_txrx_wrapper_set_flow_control_parameters() - set flow control parameters
* @cfg_ctx: cfg context
* @cfg_param: cfg parameters
*
* Return: none
*/
static void
ol_txrx_wrapper_set_flow_control_parameters(struct cdp_cfg *cfg_pdev,
void *cfg_param)
{
return ol_tx_set_flow_control_parameters(
cfg_pdev,
(struct txrx_pdev_cfg_param_t *)cfg_param);
}
/**
* ol_txrx_get_cfg() - get ini/cgf values in legacy dp
* @soc: soc context
* @cfg_param: cfg parameters
*
* Return: none
*/
static uint32_t ol_txrx_get_cfg(void *soc, enum cdp_dp_cfg cfg)
{
struct txrx_pdev_cfg_t *cfg_ctx;
ol_txrx_pdev_handle pdev = cds_get_context(QDF_MODULE_ID_TXRX);
uint32_t value = 0;
if (!pdev) {
qdf_print("pdev is NULL");
return 0;
}
cfg_ctx = (struct txrx_pdev_cfg_t *)(pdev->ctrl_pdev);
switch (cfg) {
case cfg_dp_enable_data_stall:
value = cfg_ctx->enable_data_stall_detection;
break;
case cfg_dp_enable_ip_tcp_udp_checksum_offload:
value = cfg_ctx->ip_tcp_udp_checksum_offload;
break;
case cfg_dp_tso_enable:
value = cfg_ctx->tso_enable;
break;
case cfg_dp_lro_enable:
value = cfg_ctx->lro_enable;
break;
case cfg_dp_gro_enable:
value = cfg_ctx->gro_enable;
break;
#ifdef QCA_LL_TX_FLOW_CONTROL_V2
case cfg_dp_tx_flow_start_queue_offset:
value = cfg_ctx->tx_flow_start_queue_offset;
break;
case cfg_dp_tx_flow_stop_queue_threshold:
value = cfg_ctx->tx_flow_stop_queue_th;
break;
#endif
case cfg_dp_ipa_uc_tx_buf_size:
value = cfg_ctx->uc_tx_buffer_size;
break;
case cfg_dp_ipa_uc_tx_partition_base:
value = cfg_ctx->uc_tx_partition_base;
break;
case cfg_dp_ipa_uc_rx_ind_ring_count:
value = cfg_ctx->uc_rx_indication_ring_count;
break;
case cfg_dp_enable_flow_steering:
value = cfg_ctx->enable_flow_steering;
break;
case cfg_dp_reorder_offload_supported:
value = cfg_ctx->is_full_reorder_offload;
break;
case cfg_dp_ce_classify_enable:
value = cfg_ctx->ce_classify_enabled;
break;
case cfg_dp_disable_intra_bss_fwd:
value = cfg_ctx->disable_intra_bss_fwd;
break;
default:
value = 0;
break;
}
return value;
}
#ifdef WDI_EVENT_ENABLE
void *ol_get_pldev(struct cdp_pdev *txrx_pdev)
{
struct ol_txrx_pdev_t *pdev =
(struct ol_txrx_pdev_t *)txrx_pdev;
if (pdev)
return pdev->pl_dev;
return NULL;
}
#endif
/**
* ol_register_packetdump_callback() - registers
* tx data packet, tx mgmt. packet and rx data packet
* dump callback handler.
*
* @ol_tx_packetdump_cb: tx packetdump cb
* @ol_rx_packetdump_cb: rx packetdump cb
*
* This function is used to register tx data pkt, tx mgmt.
* pkt and rx data pkt dump callback
*
* Return: None
*
*/
static inline
void ol_register_packetdump_callback(ol_txrx_pktdump_cb ol_tx_packetdump_cb,
ol_txrx_pktdump_cb ol_rx_packetdump_cb)
{
ol_txrx_pdev_handle pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
pdev->ol_tx_packetdump_cb = ol_tx_packetdump_cb;
pdev->ol_rx_packetdump_cb = ol_rx_packetdump_cb;
}
/**
* ol_deregister_packetdump_callback() - deregidters
* tx data packet, tx mgmt. packet and rx data packet
* dump callback handler
*
* This function is used to deregidter tx data pkt.,
* tx mgmt. pkt and rx data pkt. dump callback
*
* Return: None
*
*/
static inline
void ol_deregister_packetdump_callback(void)
{
ol_txrx_pdev_handle pdev = cds_get_context(QDF_MODULE_ID_TXRX);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
pdev->ol_tx_packetdump_cb = NULL;
pdev->ol_rx_packetdump_cb = NULL;
}
static struct cdp_cmn_ops ol_ops_cmn = {
.txrx_soc_attach_target = ol_txrx_soc_attach_target,
.txrx_vdev_attach = ol_txrx_vdev_attach,
.txrx_vdev_detach = ol_txrx_vdev_detach,
.txrx_pdev_attach = ol_txrx_pdev_attach,
.txrx_pdev_attach_target = ol_txrx_pdev_attach_target,
.txrx_pdev_post_attach = ol_txrx_pdev_post_attach,
.txrx_pdev_pre_detach = ol_txrx_pdev_pre_detach,
.txrx_pdev_detach = ol_txrx_pdev_detach,
.txrx_peer_create = ol_txrx_peer_attach,
.txrx_peer_setup = NULL,
.txrx_peer_teardown = NULL,
.txrx_peer_delete = ol_txrx_peer_detach,
.txrx_peer_delete_sync = ol_txrx_peer_detach_sync,
.txrx_vdev_register = ol_txrx_vdev_register,
.txrx_soc_detach = ol_txrx_soc_detach,
.txrx_get_vdev_mac_addr = ol_txrx_get_vdev_mac_addr,
.txrx_get_vdev_from_vdev_id = ol_txrx_wrapper_get_vdev_from_vdev_id,
.txrx_get_ctrl_pdev_from_vdev = ol_txrx_get_ctrl_pdev_from_vdev,
.txrx_get_mon_vdev_from_pdev = ol_txrx_get_mon_vdev_from_pdev,
.txrx_mgmt_send_ext = ol_txrx_mgmt_send_ext,
.txrx_mgmt_tx_cb_set = ol_txrx_mgmt_tx_cb_set,
.txrx_data_tx_cb_set = ol_txrx_data_tx_cb_set,
.txrx_peer_unmap_sync_cb_set = ol_txrx_peer_unmap_sync_cb_set,
.txrx_get_tx_pending = ol_txrx_get_tx_pending,
.flush_cache_rx_queue = ol_txrx_flush_cache_rx_queue,
.txrx_fw_stats_get = ol_txrx_fw_stats_get,
.display_stats = ol_txrx_display_stats,
.txrx_get_cfg = ol_txrx_get_cfg,
.txrx_pdev_set_ctrl_pdev = ol_txrx_pdev_set_ctrl_pdev,
/* TODO: Add other functions */
};
static struct cdp_misc_ops ol_ops_misc = {
.set_ibss_vdev_heart_beat_timer =
ol_txrx_set_ibss_vdev_heart_beat_timer,
#ifdef CONFIG_HL_SUPPORT
.set_wmm_param = ol_txrx_set_wmm_param,
#endif /* CONFIG_HL_SUPPORT */
.bad_peer_txctl_set_setting = ol_txrx_bad_peer_txctl_set_setting,
.bad_peer_txctl_update_threshold =
ol_txrx_bad_peer_txctl_update_threshold,
.hl_tdls_flag_reset = ol_txrx_hl_tdls_flag_reset,
.tx_non_std = ol_tx_non_std,
.get_vdev_id = ol_txrx_get_vdev_id,
.get_tx_ack_stats = ol_txrx_get_tx_ack_stats,
.set_wisa_mode = ol_txrx_set_wisa_mode,
.txrx_data_stall_cb_register = ol_register_data_stall_detect_cb,
.txrx_data_stall_cb_deregister = ol_deregister_data_stall_detect_cb,
.txrx_post_data_stall_event = ol_txrx_post_data_stall_event,
#ifdef FEATURE_RUNTIME_PM
.runtime_suspend = ol_txrx_runtime_suspend,
.runtime_resume = ol_txrx_runtime_resume,
#endif /* FEATURE_RUNTIME_PM */
.get_opmode = ol_txrx_get_opmode,
.mark_first_wakeup_packet = ol_tx_mark_first_wakeup_packet,
.update_mac_id = ol_txrx_update_mac_id,
.flush_rx_frames = ol_txrx_wrapper_flush_rx_frames,
.get_intra_bss_fwd_pkts_count = ol_get_intra_bss_fwd_pkts_count,
.pkt_log_init = htt_pkt_log_init,
.pkt_log_con_service = ol_txrx_pkt_log_con_service,
.register_pktdump_cb = ol_register_packetdump_callback,
.unregister_pktdump_cb = ol_deregister_packetdump_callback,
#ifdef QCA_SUPPORT_TXRX_DRIVER_TCP_DEL_ACK
.pdev_reset_driver_del_ack = ol_tx_pdev_reset_driver_del_ack,
.vdev_set_driver_del_ack_enable = ol_tx_vdev_set_driver_del_ack_enable
#endif
};
static struct cdp_flowctl_ops ol_ops_flowctl = {
.register_pause_cb = ol_txrx_register_pause_cb,
#ifdef QCA_LL_TX_FLOW_CONTROL_V2
.set_desc_global_pool_size = ol_tx_set_desc_global_pool_size,
.dump_flow_pool_info = ol_tx_dump_flow_pool_info,
.tx_desc_thresh_reached = ol_tx_desc_thresh_reached,
#endif /* QCA_LL_TX_FLOW_CONTROL_V2 */
};
#if defined(QCA_LL_LEGACY_TX_FLOW_CONTROL)
static struct cdp_lflowctl_ops ol_ops_l_flowctl = {
.register_tx_flow_control = ol_txrx_register_tx_flow_control,
.deregister_tx_flow_control_cb = ol_txrx_deregister_tx_flow_control_cb,
.flow_control_cb = ol_txrx_flow_control_cb,
.get_tx_resource = ol_txrx_get_tx_resource,
.ll_set_tx_pause_q_depth = ol_txrx_ll_set_tx_pause_q_depth,
.vdev_flush = ol_txrx_vdev_flush,
.vdev_pause = ol_txrx_vdev_pause,
.vdev_unpause = ol_txrx_vdev_unpause
}; /* QCA_LL_LEGACY_TX_FLOW_CONTROL */
#elif defined(QCA_HL_NETDEV_FLOW_CONTROL)
static struct cdp_lflowctl_ops ol_ops_l_flowctl = {
.register_tx_flow_control = ol_txrx_register_hl_flow_control,
.vdev_flush = ol_txrx_vdev_flush,
.vdev_pause = ol_txrx_vdev_pause,
.vdev_unpause = ol_txrx_vdev_unpause,
.set_vdev_os_queue_status = ol_txrx_set_vdev_os_queue_status,
.set_vdev_tx_desc_limit = ol_txrx_set_vdev_tx_desc_limit
};
#else /* QCA_HL_NETDEV_FLOW_CONTROL */
static struct cdp_lflowctl_ops ol_ops_l_flowctl = { };
#endif
#ifdef IPA_OFFLOAD
static struct cdp_ipa_ops ol_ops_ipa = {
.ipa_get_resource = ol_txrx_ipa_uc_get_resource,
.ipa_set_doorbell_paddr = ol_txrx_ipa_uc_set_doorbell_paddr,
.ipa_set_active = ol_txrx_ipa_uc_set_active,
.ipa_op_response = ol_txrx_ipa_uc_op_response,
.ipa_register_op_cb = ol_txrx_ipa_uc_register_op_cb,
.ipa_get_stat = ol_txrx_ipa_uc_get_stat,
.ipa_tx_data_frame = ol_tx_send_ipa_data_frame,
.ipa_set_uc_tx_partition_base = ol_cfg_set_ipa_uc_tx_partition_base,
.ipa_enable_autonomy = ol_txrx_ipa_enable_autonomy,
.ipa_disable_autonomy = ol_txrx_ipa_disable_autonomy,
.ipa_setup = ol_txrx_ipa_setup,
.ipa_cleanup = ol_txrx_ipa_cleanup,
.ipa_setup_iface = ol_txrx_ipa_setup_iface,
.ipa_cleanup_iface = ol_txrx_ipa_cleanup_iface,
.ipa_enable_pipes = ol_txrx_ipa_enable_pipes,
.ipa_disable_pipes = ol_txrx_ipa_disable_pipes,
.ipa_set_perf_level = ol_txrx_ipa_set_perf_level,
#ifdef FEATURE_METERING
.ipa_uc_get_share_stats = ol_txrx_ipa_uc_get_share_stats,
.ipa_uc_set_quota = ol_txrx_ipa_uc_set_quota
#endif
};
#endif
#ifdef RECEIVE_OFFLOAD
static struct cdp_rx_offld_ops ol_rx_offld_ops = {
.register_rx_offld_flush_cb = ol_register_offld_flush_cb,
.deregister_rx_offld_flush_cb = ol_deregister_offld_flush_cb
};
#endif
static struct cdp_bus_ops ol_ops_bus = {
.bus_suspend = ol_txrx_bus_suspend,
.bus_resume = ol_txrx_bus_resume
};
#ifdef WLAN_FEATURE_DSRC
static struct cdp_ocb_ops ol_ops_ocb = {
.set_ocb_chan_info = ol_txrx_set_ocb_chan_info,
.get_ocb_chan_info = ol_txrx_get_ocb_chan_info
};
#endif
static struct cdp_throttle_ops ol_ops_throttle = {
#ifdef QCA_SUPPORT_TX_THROTTLE
.throttle_init_period = ol_tx_throttle_init_period,
.throttle_set_level = ol_tx_throttle_set_level
#endif /* QCA_SUPPORT_TX_THROTTLE */
};
static struct cdp_mob_stats_ops ol_ops_mob_stats = {
.clear_stats = ol_txrx_clear_stats,
.stats = ol_txrx_stats
};
static struct cdp_cfg_ops ol_ops_cfg = {
.set_cfg_rx_fwd_disabled = ol_set_cfg_rx_fwd_disabled,
.set_cfg_packet_log_enabled = ol_set_cfg_packet_log_enabled,
.cfg_attach = ol_pdev_cfg_attach,
.vdev_rx_set_intrabss_fwd = ol_vdev_rx_set_intrabss_fwd,
.is_rx_fwd_disabled = ol_txrx_is_rx_fwd_disabled,
.tx_set_is_mgmt_over_wmi_enabled = ol_tx_set_is_mgmt_over_wmi_enabled,
.is_high_latency = ol_txrx_wrapper_cfg_is_high_latency,
.set_flow_control_parameters =
ol_txrx_wrapper_set_flow_control_parameters,
.set_flow_steering = ol_set_cfg_flow_steering,
.set_ptp_rx_opt_enabled = ol_set_cfg_ptp_rx_opt_enabled,
.set_new_htt_msg_format =
ol_txrx_set_new_htt_msg_format,
.set_peer_unmap_conf_support = ol_txrx_set_peer_unmap_conf_support,
.get_peer_unmap_conf_support = ol_txrx_get_peer_unmap_conf_support,
.set_tx_compl_tsf64 = ol_txrx_set_tx_compl_tsf64,
.get_tx_compl_tsf64 = ol_txrx_get_tx_compl_tsf64,
};
static struct cdp_peer_ops ol_ops_peer = {
.register_peer = ol_txrx_wrapper_register_peer,
.clear_peer = ol_txrx_clear_peer,
.peer_get_ref_by_addr = ol_txrx_wrapper_peer_get_ref_by_addr,
.peer_release_ref = ol_txrx_wrapper_peer_release_ref,
.find_peer_by_addr = ol_txrx_wrapper_find_peer_by_addr,
.find_peer_by_addr_and_vdev = ol_txrx_find_peer_by_addr_and_vdev,
.local_peer_id = ol_txrx_local_peer_id,
.peer_find_by_local_id = ol_txrx_wrapper_peer_find_by_local_id,
.peer_state_update = ol_txrx_wrapper_peer_state_update,
.get_vdevid = ol_txrx_get_vdevid,
.get_vdev_by_peer_addr = ol_txrx_wrapper_get_vdev_by_peer_addr,
.register_ocb_peer = ol_txrx_register_ocb_peer,
.peer_get_peer_mac_addr = ol_txrx_peer_get_peer_mac_addr,
.get_peer_state = ol_txrx_get_peer_state,
.get_vdev_for_peer = ol_txrx_get_vdev_for_peer,
.update_ibss_add_peer_num_of_vdev =
ol_txrx_update_ibss_add_peer_num_of_vdev,
.remove_peers_for_vdev = ol_txrx_remove_peers_for_vdev,
.remove_peers_for_vdev_no_lock = ol_txrx_remove_peers_for_vdev_no_lock,
#if defined(CONFIG_HL_SUPPORT) && defined(FEATURE_WLAN_TDLS)
.copy_mac_addr_raw = ol_txrx_copy_mac_addr_raw,
.add_last_real_peer = ol_txrx_add_last_real_peer,
.is_vdev_restore_last_peer = is_vdev_restore_last_peer,
.update_last_real_peer = ol_txrx_update_last_real_peer,
#endif /* CONFIG_HL_SUPPORT */
.peer_detach_force_delete = ol_txrx_peer_detach_force_delete,
};
static struct cdp_tx_delay_ops ol_ops_delay = {
#ifdef QCA_COMPUTE_TX_DELAY
.tx_delay = ol_tx_delay,
.tx_delay_hist = ol_tx_delay_hist,
.tx_packet_count = ol_tx_packet_count,
.tx_set_compute_interval = ol_tx_set_compute_interval
#endif /* QCA_COMPUTE_TX_DELAY */
};
static struct cdp_pmf_ops ol_ops_pmf = {
.get_pn_info = ol_txrx_get_pn_info
};
static struct cdp_ctrl_ops ol_ops_ctrl = {
.txrx_get_pldev = ol_get_pldev,
.txrx_wdi_event_sub = wdi_event_sub,
.txrx_wdi_event_unsub = wdi_event_unsub,
};
/* WINplatform specific structures */
static struct cdp_me_ops ol_ops_me = {
/* EMPTY FOR MCL */
};
static struct cdp_mon_ops ol_ops_mon = {
.txrx_monitor_record_channel = ol_htt_mon_note_chan,
};
static struct cdp_host_stats_ops ol_ops_host_stats = {
/* EMPTY FOR MCL */
};
static struct cdp_wds_ops ol_ops_wds = {
/* EMPTY FOR MCL */
};
static struct cdp_raw_ops ol_ops_raw = {
/* EMPTY FOR MCL */
};
static struct cdp_ops ol_txrx_ops = {
.cmn_drv_ops = &ol_ops_cmn,
.ctrl_ops = &ol_ops_ctrl,
.me_ops = &ol_ops_me,
.mon_ops = &ol_ops_mon,
.host_stats_ops = &ol_ops_host_stats,
.wds_ops = &ol_ops_wds,
.raw_ops = &ol_ops_raw,
.misc_ops = &ol_ops_misc,
.cfg_ops = &ol_ops_cfg,
.flowctl_ops = &ol_ops_flowctl,
.l_flowctl_ops = &ol_ops_l_flowctl,
#ifdef IPA_OFFLOAD
.ipa_ops = &ol_ops_ipa,
#endif
#ifdef RECEIVE_OFFLOAD
.rx_offld_ops = &ol_rx_offld_ops,
#endif
.bus_ops = &ol_ops_bus,
#ifdef WLAN_FEATURE_DSRC
.ocb_ops = &ol_ops_ocb,
#endif
.peer_ops = &ol_ops_peer,
.throttle_ops = &ol_ops_throttle,
.mob_stats_ops = &ol_ops_mob_stats,
.delay_ops = &ol_ops_delay,
.pmf_ops = &ol_ops_pmf
};
ol_txrx_soc_handle ol_txrx_soc_attach(void *scn_handle,
struct ol_if_ops *dp_ol_if_ops)
{
struct ol_txrx_soc_t *soc;
soc = qdf_mem_malloc(sizeof(*soc));
if (!soc)
return NULL;
soc->psoc = scn_handle;
soc->cdp_soc.ops = &ol_txrx_ops;
soc->cdp_soc.ol_ops = dp_ol_if_ops;
return ol_txrx_soc_t_to_cdp_soc_t(soc);
}
bool ol_txrx_get_new_htt_msg_format(struct ol_txrx_pdev_t *pdev)
{
if (!pdev) {
qdf_print("%s: pdev is NULL\n", __func__);
return false;
}
return pdev->new_htt_msg_format;
}
void ol_txrx_set_new_htt_msg_format(uint8_t val)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
qdf_print("%s: pdev is NULL\n", __func__);
return;
}
pdev->new_htt_msg_format = val;
}
bool ol_txrx_get_peer_unmap_conf_support(void)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return false;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
qdf_print("%s: pdev is NULL\n", __func__);
return false;
}
return pdev->enable_peer_unmap_conf_support;
}
void ol_txrx_set_peer_unmap_conf_support(bool val)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
qdf_print("%s: pdev is NULL\n", __func__);
return;
}
pdev->enable_peer_unmap_conf_support = val;
}
#ifdef WLAN_FEATURE_TSF_PLUS
bool ol_txrx_get_tx_compl_tsf64(void)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return false;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
qdf_print("%s: pdev is NULL\n", __func__);
return false;
}
return pdev->enable_tx_compl_tsf64;
}
void ol_txrx_set_tx_compl_tsf64(bool val)
{
struct ol_txrx_soc_t *soc = cds_get_context(QDF_MODULE_ID_SOC);
ol_txrx_pdev_handle pdev;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, OL_TXRX_PDEV_ID);
if (!pdev) {
ol_txrx_err("pdev is NULL");
return;
}
pdev->enable_tx_compl_tsf64 = val;
}
#else
bool ol_txrx_get_tx_compl_tsf64(void)
{
return false;
}
void ol_txrx_set_tx_compl_tsf64(bool val)
{
}
#endif