/* * Copyright (c) 2015-2018 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 /* OS abstraction libraries */ #include /* qdf_nbuf_t, etc. */ #include /* qdf_atomic_read, etc. */ #include /* qdf_unlikely */ #include "dp_types.h" #include "dp_tx_desc.h" #include #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(void *ctx) { struct dp_soc *soc = ctx; 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) { QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "%s: invalid flow_pool_id %d", __func__, 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); 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) { QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "%s: pool or soc is NULL", __func__); QDF_ASSERT(0); return ENOMEM; } qdf_spin_lock_bh(&pool->flow_pool_lock); if (!pool->pool_create_cnt) { QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "flow pool either not created or alread deleted"); qdf_spin_unlock_bh(&pool->flow_pool_lock); return -ENOENT; } pool->pool_create_cnt--; if (pool->pool_create_cnt) { QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "%s: pool is still attached, pending detach %d", __func__, pool->pool_create_cnt); qdf_spin_unlock_bh(&pool->flow_pool_lock); return -EAGAIN; } if (pool->avail_desc < pool->pool_size) { pool->status = FLOW_POOL_INVALID; qdf_spin_unlock_bh(&pool->flow_pool_lock); 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 = (struct dp_vdev *)cdp_get_vdev_from_vdev_id((void *)soc, (struct cdp_pdev *)pdev, 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 = (struct dp_vdev *)cdp_get_vdev_from_vdev_id((void *)soc, (struct cdp_pdev *)pdev, 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; QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO, "%s: flow_id %d flow_type %d flow_pool_id %d flow_pool_size %d", __func__, flow_id, flow_type, flow_pool_id, flow_pool_size); if (qdf_unlikely(!soc)) { QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "%s: soc is NULL", __func__); 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 == NULL) { QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "%s: creation of flow_pool %d size %d failed", __func__, 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: QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, "%s: flow type %d not supported !!!", __func__, 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_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) { 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, struct cdp_pdev *pdev, uint8_t vdev_id) { struct dp_soc *soc = (struct dp_soc *)handle; int tx_ring_size = wlan_cfg_tx_ring_size(soc->wlan_cfg_ctx); return (dp_tx_flow_pool_map_handler((struct dp_pdev *)pdev, vdev_id, FLOW_TYPE_VDEV, vdev_id, tx_ring_size)); } void dp_tx_flow_pool_unmap(struct cdp_soc_t *soc, struct cdp_pdev *pdev, uint8_t vdev_id) { return(dp_tx_flow_pool_unmap_handler((struct dp_pdev *)pdev, vdev_id, FLOW_TYPE_VDEV, vdev_id)); }