Files
android_kernel_samsung_sm86…/dp/wifi3.0/dp_tx_flow_control.c
Rakesh Pillai dce01374cd qcacmn: cdp: Convergence of cdp_flowctl_ops
Currently the cdp apis are given pdev/vdev/peer
handle as its arguments, which is directly
accessed in those APIs. This can cause a
race-condition in access of the respective
handles if it has been deleted in parallel.

Hence as a part of cdp convergence, pass only
the pdev/vdev id or peer mac address, which will be
used to get the respective handles, and hence
avoiding the unwanted access of the handles if
it has been deleted.

Converged flowctl_ops
- flow_pool_map_handler
- flow_pool_unmap_handler
- dump_flow_pool_info
- tx_desc_thresh_reached

CRs-Fixed: 2539812
Change-Id: I084d3878df84778622340e87bddf90acd3e669d6
2019-11-11 23:08:22 -08:00

604 lines
16 KiB
C

/*
* Copyright (c) 2015-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.
*/
#include <cds_api.h>
/* OS abstraction libraries */
#include <qdf_nbuf.h> /* qdf_nbuf_t, etc. */
#include <qdf_atomic.h> /* qdf_atomic_read, etc. */
#include <qdf_util.h> /* qdf_unlikely */
#include "dp_types.h"
#include "dp_tx_desc.h"
#include <cdp_txrx_handle.h>
#include "dp_internal.h"
#define INVALID_FLOW_ID 0xFF
#define MAX_INVALID_BIN 3
#ifdef QCA_AC_BASED_FLOW_CONTROL
/**
* dp_tx_initialize_threshold() - Threshold of flow Pool initialization
* @pool: flow_pool
* @stop_threshold: stop threshold of certian AC
* @start_threshold: start threshold of certian AC
* @flow_pool_size: flow pool size
*
* Return: none
*/
static inline void
dp_tx_initialize_threshold(struct dp_tx_desc_pool_s *pool,
uint32_t start_threshold,
uint32_t stop_threshold,
uint16_t flow_pool_size)
{
/* BE_BK threshold is same as previous threahold */
pool->start_th[DP_TH_BE_BK] = (start_threshold
* flow_pool_size) / 100;
pool->stop_th[DP_TH_BE_BK] = (stop_threshold
* flow_pool_size) / 100;
/* Update VI threshold based on BE_BK threashold */
pool->start_th[DP_TH_VI] = (pool->start_th[DP_TH_BE_BK]
* FL_TH_VI_PERCENTAGE) / 100;
pool->stop_th[DP_TH_VI] = (pool->stop_th[DP_TH_BE_BK]
* FL_TH_VI_PERCENTAGE) / 100;
/* Update VO threshold based on BE_BK threashold */
pool->start_th[DP_TH_VO] = (pool->start_th[DP_TH_BE_BK]
* FL_TH_VO_PERCENTAGE) / 100;
pool->stop_th[DP_TH_VO] = (pool->stop_th[DP_TH_BE_BK]
* FL_TH_VO_PERCENTAGE) / 100;
/* Update High Priority threshold based on BE_BK threashold */
pool->start_th[DP_TH_HI] = (pool->start_th[DP_TH_BE_BK]
* FL_TH_HI_PERCENTAGE) / 100;
pool->stop_th[DP_TH_HI] = (pool->stop_th[DP_TH_BE_BK]
* FL_TH_HI_PERCENTAGE) / 100;
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: tx flow control threshold is set, pool size is %d",
__func__, flow_pool_size);
}
/**
* dp_tx_flow_pool_reattach() - Reattach flow_pool
* @pool: flow_pool
*
* Return: none
*/
static inline void
dp_tx_flow_pool_reattach(struct dp_tx_desc_pool_s *pool)
{
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: flow pool already allocated, attached %d times",
__func__, pool->pool_create_cnt);
if (pool->avail_desc > pool->start_th[DP_TH_BE_BK])
pool->status = FLOW_POOL_ACTIVE_UNPAUSED;
if (pool->avail_desc <= pool->start_th[DP_TH_BE_BK] &&
pool->avail_desc > pool->start_th[DP_TH_VI])
pool->status = FLOW_POOL_BE_BK_PAUSED;
else if (pool->avail_desc <= pool->start_th[DP_TH_VI] &&
pool->avail_desc > pool->start_th[DP_TH_VO])
pool->status = FLOW_POOL_VI_PAUSED;
else if (pool->avail_desc <= pool->start_th[DP_TH_VO] &&
pool->avail_desc > pool->start_th[DP_TH_HI])
pool->status = FLOW_POOL_VO_PAUSED;
else
pool->status = FLOW_POOL_ACTIVE_PAUSED;
pool->pool_create_cnt++;
}
/**
* dp_tx_flow_pool_dump_threshold() - Dump threshold of the flow_pool
* @pool: flow_pool
*
* Return: none
*/
static inline void
dp_tx_flow_pool_dump_threshold(struct dp_tx_desc_pool_s *pool)
{
int i;
for (i = 0; i < FL_TH_MAX; i++) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Level %d :: Start threshold %d :: Stop threshold %d",
i, pool->start_th[i], pool->stop_th[i]);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Level %d :: Maximun pause time %lu ms",
i, pool->max_pause_time[i]);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Level %d :: Latest pause timestamp %lu",
i, pool->latest_pause_time[i]);
}
}
#else
static inline void
dp_tx_initialize_threshold(struct dp_tx_desc_pool_s *pool,
uint32_t start_threshold,
uint32_t stop_threshold,
uint16_t flow_pool_size)
{
/* INI is in percentage so divide by 100 */
pool->start_th = (start_threshold * flow_pool_size) / 100;
pool->stop_th = (stop_threshold * flow_pool_size) / 100;
}
static inline void
dp_tx_flow_pool_reattach(struct dp_tx_desc_pool_s *pool)
{
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: flow pool already allocated, attached %d times",
__func__, pool->pool_create_cnt);
if (pool->avail_desc > pool->start_th)
pool->status = FLOW_POOL_ACTIVE_UNPAUSED;
else
pool->status = FLOW_POOL_ACTIVE_PAUSED;
pool->pool_create_cnt++;
}
static inline void
dp_tx_flow_pool_dump_threshold(struct dp_tx_desc_pool_s *pool)
{
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Start threshold %d :: Stop threshold %d",
pool->start_th, pool->stop_th);
}
#endif
/**
* dp_tx_dump_flow_pool_info() - dump global_pool and flow_pool info
*
* @ctx: Handle to struct dp_soc.
*
* Return: none
*/
void dp_tx_dump_flow_pool_info(struct cdp_soc_t *soc_hdl)
{
struct dp_soc *soc = cdp_soc_t_to_dp_soc(soc_hdl);
struct dp_txrx_pool_stats *pool_stats = &soc->pool_stats;
struct dp_tx_desc_pool_s *pool = NULL;
struct dp_tx_desc_pool_s tmp_pool;
int i;
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"No of pool map received %d", pool_stats->pool_map_count);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"No of pool unmap received %d", pool_stats->pool_unmap_count);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Pkt dropped due to unavailablity of pool %d",
pool_stats->pkt_drop_no_pool);
/*
* Nested spin lock.
* Always take in below order.
* flow_pool_array_lock -> flow_pool_lock
*/
qdf_spin_lock_bh(&soc->flow_pool_array_lock);
for (i = 0; i < MAX_TXDESC_POOLS; i++) {
pool = &soc->tx_desc[i];
if (pool->status > FLOW_POOL_INVALID)
continue;
qdf_spin_lock_bh(&pool->flow_pool_lock);
qdf_mem_copy(&tmp_pool, pool, sizeof(tmp_pool));
qdf_spin_unlock_bh(&pool->flow_pool_lock);
qdf_spin_unlock_bh(&soc->flow_pool_array_lock);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "\n");
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Flow_pool_id %d :: status %d",
tmp_pool.flow_pool_id, tmp_pool.status);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Total %d :: Available %d",
tmp_pool.pool_size, tmp_pool.avail_desc);
dp_tx_flow_pool_dump_threshold(&tmp_pool);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Member flow_id %d :: flow_type %d",
tmp_pool.flow_pool_id, tmp_pool.flow_type);
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"Pkt dropped due to unavailablity of descriptors %d",
tmp_pool.pkt_drop_no_desc);
qdf_spin_lock_bh(&soc->flow_pool_array_lock);
}
qdf_spin_unlock_bh(&soc->flow_pool_array_lock);
}
/**
* dp_tx_clear_flow_pool_stats() - clear flow pool statistics
*
* @soc: Handle to struct dp_soc.
*
* Return: None
*/
void dp_tx_clear_flow_pool_stats(struct dp_soc *soc)
{
if (!soc) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: soc is null", __func__);
return;
}
qdf_mem_zero(&soc->pool_stats, sizeof(soc->pool_stats));
}
/**
* dp_tx_create_flow_pool() - create flow pool
* @soc: Handle to struct dp_soc
* @flow_pool_id: flow pool id
* @flow_pool_size: flow pool size
*
* Return: flow_pool pointer / NULL for error
*/
struct dp_tx_desc_pool_s *dp_tx_create_flow_pool(struct dp_soc *soc,
uint8_t flow_pool_id, uint16_t flow_pool_size)
{
struct dp_tx_desc_pool_s *pool;
uint32_t stop_threshold;
uint32_t start_threshold;
if (flow_pool_id >= MAX_TXDESC_POOLS) {
dp_err("invalid flow_pool_id %d", flow_pool_id);
return NULL;
}
pool = &soc->tx_desc[flow_pool_id];
qdf_spin_lock_bh(&pool->flow_pool_lock);
if ((pool->status != FLOW_POOL_INACTIVE) || pool->pool_create_cnt) {
dp_tx_flow_pool_reattach(pool);
qdf_spin_unlock_bh(&pool->flow_pool_lock);
dp_err("cannot alloc desc, status=%d, create_cnt=%d",
pool->status, pool->pool_create_cnt);
return pool;
}
if (dp_tx_desc_pool_alloc(soc, flow_pool_id, flow_pool_size)) {
qdf_spin_unlock_bh(&pool->flow_pool_lock);
return NULL;
}
stop_threshold = wlan_cfg_get_tx_flow_stop_queue_th(soc->wlan_cfg_ctx);
start_threshold = stop_threshold +
wlan_cfg_get_tx_flow_start_queue_offset(soc->wlan_cfg_ctx);
pool->flow_pool_id = flow_pool_id;
pool->pool_size = flow_pool_size;
pool->avail_desc = flow_pool_size;
pool->status = FLOW_POOL_ACTIVE_UNPAUSED;
dp_tx_initialize_threshold(pool, start_threshold, stop_threshold,
flow_pool_size);
pool->pool_create_cnt++;
qdf_spin_unlock_bh(&pool->flow_pool_lock);
return pool;
}
/**
* dp_tx_delete_flow_pool() - delete flow pool
* @soc: Handle to struct dp_soc
* @pool: flow pool pointer
* @force: free pool forcefully
*
* Delete flow_pool if all tx descriptors are available.
* Otherwise put it in FLOW_POOL_INVALID state.
* If force is set then pull all available descriptors to
* global pool.
*
* Return: 0 for success or error
*/
int dp_tx_delete_flow_pool(struct dp_soc *soc, struct dp_tx_desc_pool_s *pool,
bool force)
{
if (!soc || !pool) {
dp_err("pool or soc is NULL");
QDF_ASSERT(0);
return ENOMEM;
}
dp_info("pool create_cnt=%d, avail_desc=%d, size=%d, status=%d",
pool->pool_create_cnt, pool->avail_desc,
pool->pool_size, pool->status);
qdf_spin_lock_bh(&pool->flow_pool_lock);
if (!pool->pool_create_cnt) {
qdf_spin_unlock_bh(&pool->flow_pool_lock);
dp_err("flow pool either not created or alread deleted");
return -ENOENT;
}
pool->pool_create_cnt--;
if (pool->pool_create_cnt) {
qdf_spin_unlock_bh(&pool->flow_pool_lock);
dp_err("pool is still attached, pending detach %d",
pool->pool_create_cnt);
return -EAGAIN;
}
if (pool->avail_desc < pool->pool_size) {
pool->status = FLOW_POOL_INVALID;
qdf_spin_unlock_bh(&pool->flow_pool_lock);
dp_err("avail desc less than pool size");
return -EAGAIN;
}
/* We have all the descriptors for the pool, we can delete the pool */
dp_tx_desc_pool_free(soc, pool->flow_pool_id);
qdf_spin_unlock_bh(&pool->flow_pool_lock);
return 0;
}
/**
* dp_tx_flow_pool_vdev_map() - Map flow_pool with vdev
* @pdev: Handle to struct dp_pdev
* @pool: flow_pool
* @vdev_id: flow_id /vdev_id
*
* Return: none
*/
static void dp_tx_flow_pool_vdev_map(struct dp_pdev *pdev,
struct dp_tx_desc_pool_s *pool, uint8_t vdev_id)
{
struct dp_vdev *vdev;
struct dp_soc *soc = pdev->soc;
vdev = dp_get_vdev_from_soc_vdev_id_wifi3(soc, vdev_id);
if (!vdev) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: invalid vdev_id %d",
__func__, vdev_id);
return;
}
vdev->pool = pool;
qdf_spin_lock_bh(&pool->flow_pool_lock);
pool->pool_owner_ctx = soc;
pool->flow_pool_id = vdev_id;
qdf_spin_unlock_bh(&pool->flow_pool_lock);
}
/**
* dp_tx_flow_pool_vdev_unmap() - Unmap flow_pool from vdev
* @pdev: Handle to struct dp_pdev
* @pool: flow_pool
* @vdev_id: flow_id /vdev_id
*
* Return: none
*/
static void dp_tx_flow_pool_vdev_unmap(struct dp_pdev *pdev,
struct dp_tx_desc_pool_s *pool, uint8_t vdev_id)
{
struct dp_vdev *vdev;
struct dp_soc *soc = pdev->soc;
vdev = dp_get_vdev_from_soc_vdev_id_wifi3(soc, vdev_id);
if (!vdev) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: invalid vdev_id %d",
__func__, vdev_id);
return;
}
vdev->pool = NULL;
}
/**
* dp_tx_flow_pool_map_handler() - Map flow_id with pool of descriptors
* @pdev: Handle to struct dp_pdev
* @flow_id: flow id
* @flow_type: flow type
* @flow_pool_id: pool id
* @flow_pool_size: pool size
*
* Process below target to host message
* HTT_T2H_MSG_TYPE_FLOW_POOL_MAP
*
* Return: none
*/
QDF_STATUS dp_tx_flow_pool_map_handler(struct dp_pdev *pdev, uint8_t flow_id,
uint8_t flow_type, uint8_t flow_pool_id, uint16_t flow_pool_size)
{
struct dp_soc *soc = pdev->soc;
struct dp_tx_desc_pool_s *pool;
enum htt_flow_type type = flow_type;
dp_info("flow_id %d flow_type %d flow_pool_id %d flow_pool_size %d",
flow_id, flow_type, flow_pool_id, flow_pool_size);
if (qdf_unlikely(!soc)) {
dp_err("soc is NULL");
return QDF_STATUS_E_FAULT;
}
soc->pool_stats.pool_map_count++;
pool = dp_tx_create_flow_pool(soc, flow_pool_id,
flow_pool_size);
if (!pool) {
dp_err("creation of flow_pool %d size %d failed",
flow_pool_id, flow_pool_size);
return QDF_STATUS_E_RESOURCES;
}
switch (type) {
case FLOW_TYPE_VDEV:
dp_tx_flow_pool_vdev_map(pdev, pool, flow_id);
break;
default:
dp_err("flow type %d not supported", type);
break;
}
return QDF_STATUS_SUCCESS;
}
/**
* dp_tx_flow_pool_unmap_handler() - Unmap flow_id from pool of descriptors
* @pdev: Handle to struct dp_pdev
* @flow_id: flow id
* @flow_type: flow type
* @flow_pool_id: pool id
*
* Process below target to host message
* HTT_T2H_MSG_TYPE_FLOW_POOL_UNMAP
*
* Return: none
*/
void dp_tx_flow_pool_unmap_handler(struct dp_pdev *pdev, uint8_t flow_id,
uint8_t flow_type, uint8_t flow_pool_id)
{
struct dp_soc *soc = pdev->soc;
struct dp_tx_desc_pool_s *pool;
enum htt_flow_type type = flow_type;
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO,
"%s: flow_id %d flow_type %d flow_pool_id %d",
__func__, flow_id, flow_type, flow_pool_id);
if (qdf_unlikely(!pdev)) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: pdev is NULL", __func__);
return;
}
soc->pool_stats.pool_unmap_count++;
pool = &soc->tx_desc[flow_pool_id];
if (!pool) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: flow_pool not available flow_pool_id %d",
__func__, type);
return;
}
switch (type) {
case FLOW_TYPE_VDEV:
dp_tx_flow_pool_vdev_unmap(pdev, pool, flow_id);
break;
default:
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"%s: flow type %d not supported !!!",
__func__, type);
return;
}
/* only delete if all descriptors are available */
dp_tx_delete_flow_pool(soc, pool, false);
}
/**
* dp_tx_flow_control_init() - Initialize tx flow control
* @tx_desc_pool: Handle to flow_pool
*
* Return: none
*/
void dp_tx_flow_control_init(struct dp_soc *soc)
{
qdf_spinlock_create(&soc->flow_pool_array_lock);
}
/**
* dp_tx_desc_pool_dealloc() - De-allocate tx desc pool
* @tx_desc_pool: Handle to flow_pool
*
* Return: none
*/
static inline void dp_tx_desc_pool_dealloc(struct dp_soc *soc)
{
struct dp_tx_desc_pool_s *tx_desc_pool;
int i;
for (i = 0; i < MAX_TXDESC_POOLS; i++) {
tx_desc_pool = &((soc)->tx_desc[i]);
if (!tx_desc_pool->desc_pages.num_pages)
continue;
if (dp_tx_desc_pool_free(soc, i) != QDF_STATUS_SUCCESS)
dp_err("Tx Desc Pool:%d Free failed", i);
}
}
/**
* dp_tx_flow_control_deinit() - Deregister fw based tx flow control
* @tx_desc_pool: Handle to flow_pool
*
* Return: none
*/
void dp_tx_flow_control_deinit(struct dp_soc *soc)
{
dp_tx_desc_pool_dealloc(soc);
qdf_spinlock_destroy(&soc->flow_pool_array_lock);
}
/**
* dp_txrx_register_pause_cb() - Register pause callback
* @ctx: Handle to struct dp_soc
* @pause_cb: Tx pause_cb
*
* Return: none
*/
QDF_STATUS dp_txrx_register_pause_cb(struct cdp_soc_t *handle,
tx_pause_callback pause_cb)
{
struct dp_soc *soc = (struct dp_soc *)handle;
if (!soc || !pause_cb) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
FL("soc or pause_cb is NULL"));
return QDF_STATUS_E_INVAL;
}
soc->pause_cb = pause_cb;
return QDF_STATUS_SUCCESS;
}
QDF_STATUS dp_tx_flow_pool_map(struct cdp_soc_t *handle, uint8_t pdev_id,
uint8_t vdev_id)
{
struct dp_soc *soc = cdp_soc_t_to_dp_soc(handle);
struct dp_pdev *pdev =
dp_get_pdev_from_soc_pdev_id_wifi3(soc, pdev_id);
int tx_ring_size = wlan_cfg_tx_ring_size(soc->wlan_cfg_ctx);
if (!pdev) {
dp_err("pdev is NULL");
return QDF_STATUS_E_INVAL;
}
return dp_tx_flow_pool_map_handler(pdev, vdev_id, FLOW_TYPE_VDEV,
vdev_id, tx_ring_size);
}
void dp_tx_flow_pool_unmap(struct cdp_soc_t *handle, uint8_t pdev_id,
uint8_t vdev_id)
{
struct dp_soc *soc = cdp_soc_t_to_dp_soc(handle);
struct dp_pdev *pdev =
dp_get_pdev_from_soc_pdev_id_wifi3(soc, pdev_id);
if (!pdev) {
dp_err("pdev is NULL");
return;
}
return dp_tx_flow_pool_unmap_handler(pdev, vdev_id,
FLOW_TYPE_VDEV, vdev_id);
}