blob: 42ca4acb46555688ea6e16e09d5c21ca436ae0fb [file] [log] [blame]
/*
* Copyright (c) 2012-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.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include <qdf_nbuf.h> /* qdf_nbuf_t, etc. */
#include <qdf_atomic.h> /* qdf_atomic_read, etc. */
#include <ol_cfg.h> /* ol_cfg_addba_retry */
#include <htt.h> /* HTT_TX_EXT_TID_MGMT */
#include <ol_htt_tx_api.h> /* htt_tx_desc_tid */
#include <ol_txrx_api.h> /* ol_txrx_vdev_handle */
#include <ol_txrx_ctrl_api.h> /* ol_txrx_sync, ol_tx_addba_conf */
#include <cdp_txrx_tx_throttle.h>
#include <ol_ctrl_txrx_api.h> /* ol_ctrl_addba_req */
#include <ol_txrx_internal.h> /* TXRX_ASSERT1, etc. */
#include <ol_tx_desc.h> /* ol_tx_desc, ol_tx_desc_frame_list_free */
#include <ol_tx.h> /* ol_tx_vdev_ll_pause_queue_send */
#include <ol_tx_sched.h> /* ol_tx_sched_notify, etc. */
#include <ol_tx_queue.h>
#include <ol_txrx.h> /* ol_tx_desc_pool_size_hl */
#include <ol_txrx_dbg.h> /* ENABLE_TX_QUEUE_LOG */
#include <qdf_types.h> /* bool */
#include "cdp_txrx_flow_ctrl_legacy.h"
#include <ol_txrx_peer_find.h>
#include <cdp_txrx_handle.h>
#ifdef QCA_LL_TX_FLOW_CONTROL_V2
/**
* ol_txrx_thermal_pause() - pause due to thermal mitigation
* @pdev: pdev handle
*
* Return: none
*/
static inline
void ol_txrx_thermal_pause(struct ol_txrx_pdev_t *pdev)
{
ol_txrx_pdev_pause(pdev, OL_TXQ_PAUSE_REASON_THERMAL_MITIGATION);
}
/**
* ol_txrx_thermal_unpause() - unpause due to thermal mitigation
* @pdev: pdev handle
*
* Return: none
*/
static inline
void ol_txrx_thermal_unpause(struct ol_txrx_pdev_t *pdev)
{
ol_txrx_pdev_unpause(pdev, OL_TXQ_PAUSE_REASON_THERMAL_MITIGATION);
}
#else
/**
* ol_txrx_thermal_pause() - pause due to thermal mitigation
* @pdev: pdev handle
*
* Return: none
*/
static inline
void ol_txrx_thermal_pause(struct ol_txrx_pdev_t *pdev)
{
}
/**
* ol_txrx_thermal_unpause() - unpause due to thermal mitigation
* @pdev: pdev handle
*
* Return: none
*/
static inline
void ol_txrx_thermal_unpause(struct ol_txrx_pdev_t *pdev)
{
ol_tx_pdev_ll_pause_queue_send_all(pdev);
}
#endif
static void ol_tx_pdev_throttle_phase_timer(void *context)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)context;
int ms;
enum throttle_level cur_level;
enum throttle_phase cur_phase;
/* update the phase */
pdev->tx_throttle.current_throttle_phase++;
if (pdev->tx_throttle.current_throttle_phase == THROTTLE_PHASE_MAX)
pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF;
if (pdev->tx_throttle.current_throttle_phase == THROTTLE_PHASE_OFF) {
/* Traffic is stopped */
ol_txrx_dbg(
"throttle phase --> OFF\n");
ol_txrx_throttle_pause(pdev);
ol_txrx_thermal_pause(pdev);
cur_level = pdev->tx_throttle.current_throttle_level;
cur_phase = pdev->tx_throttle.current_throttle_phase;
ms = pdev->tx_throttle.throttle_time_ms[cur_level][cur_phase];
if (pdev->tx_throttle.current_throttle_level !=
THROTTLE_LEVEL_0) {
ol_txrx_dbg(
"start timer %d ms\n", ms);
qdf_timer_start(&pdev->tx_throttle.
phase_timer, ms);
}
} else {
/* Traffic can go */
ol_txrx_dbg(
"throttle phase --> ON\n");
ol_txrx_throttle_unpause(pdev);
ol_txrx_thermal_unpause(pdev);
cur_level = pdev->tx_throttle.current_throttle_level;
cur_phase = pdev->tx_throttle.current_throttle_phase;
ms = pdev->tx_throttle.throttle_time_ms[cur_level][cur_phase];
if (pdev->tx_throttle.current_throttle_level !=
THROTTLE_LEVEL_0) {
ol_txrx_dbg("start timer %d ms\n", ms);
qdf_timer_start(&pdev->tx_throttle.phase_timer, ms);
}
}
}
#ifdef QCA_LL_LEGACY_TX_FLOW_CONTROL
static void ol_tx_pdev_throttle_tx_timer(void *context)
{
struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)context;
ol_tx_pdev_ll_pause_queue_send_all(pdev);
}
#endif
#ifdef CONFIG_HL_SUPPORT
/**
* ol_tx_set_throttle_phase_time() - Set the thermal mitgation throttle phase
* and time
* @pdev: the peer device object
* @level: throttle phase level
* @ms: throttle time
*
* Return: None
*/
static void
ol_tx_set_throttle_phase_time(struct ol_txrx_pdev_t *pdev, int level, int *ms)
{
qdf_timer_stop(&pdev->tx_throttle.phase_timer);
/* Set the phase */
if (level != THROTTLE_LEVEL_0) {
pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF;
*ms = pdev->tx_throttle.throttle_time_ms[level]
[THROTTLE_PHASE_OFF];
/* pause all */
ol_txrx_throttle_pause(pdev);
} else {
pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_ON;
*ms = pdev->tx_throttle.throttle_time_ms[level]
[THROTTLE_PHASE_ON];
/* unpause all */
ol_txrx_throttle_unpause(pdev);
}
}
#else
static void
ol_tx_set_throttle_phase_time(struct ol_txrx_pdev_t *pdev, int level, int *ms)
{
/* Reset the phase */
pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF;
/* Start with the new time */
*ms = pdev->tx_throttle.
throttle_time_ms[level][THROTTLE_PHASE_OFF];
qdf_timer_stop(&pdev->tx_throttle.phase_timer);
}
#endif
void ol_tx_throttle_set_level(struct cdp_soc_t *soc_hdl,
uint8_t pdev_id, int level)
{
struct ol_txrx_soc_t *soc = cdp_soc_t_to_ol_txrx_soc_t(soc_hdl);
ol_txrx_pdev_handle pdev = ol_txrx_get_pdev_from_pdev_id(soc, pdev_id);
int ms = 0;
if (level >= THROTTLE_LEVEL_MAX) {
ol_txrx_dbg("invalid throttle level set %d, ignoring", level);
return;
}
if (qdf_unlikely(!pdev)) {
ol_txrx_err("pdev is NULL");
return;
}
ol_txrx_info("Setting throttle level %d\n", level);
/* Set the current throttle level */
pdev->tx_throttle.current_throttle_level = (enum throttle_level)level;
ol_tx_set_throttle_phase_time(pdev, level, &ms);
if (level != THROTTLE_LEVEL_0)
qdf_timer_start(&pdev->tx_throttle.phase_timer, ms);
}
void ol_tx_throttle_init_period(struct cdp_soc_t *soc_hdl,
uint8_t pdev_id, int period,
uint8_t *dutycycle_level)
{
struct ol_txrx_soc_t *soc = cdp_soc_t_to_ol_txrx_soc_t(soc_hdl);
ol_txrx_pdev_handle pdev;
int i;
if (qdf_unlikely(!soc)) {
ol_txrx_err("soc is NULL");
return;
}
pdev = ol_txrx_get_pdev_from_pdev_id(soc, pdev_id);
if (qdf_unlikely(!pdev)) {
ol_txrx_err("pdev is NULL");
return;
}
/* Set the current throttle level */
pdev->tx_throttle.throttle_period_ms = period;
ol_txrx_dbg("level OFF ON\n");
for (i = 0; i < THROTTLE_LEVEL_MAX; i++) {
pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_ON] =
pdev->tx_throttle.throttle_period_ms -
((dutycycle_level[i] *
pdev->tx_throttle.throttle_period_ms) / 100);
pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_OFF] =
pdev->tx_throttle.throttle_period_ms -
pdev->tx_throttle.throttle_time_ms[
i][THROTTLE_PHASE_ON];
ol_txrx_dbg("%d %d %d\n", i,
pdev->tx_throttle.
throttle_time_ms[i][THROTTLE_PHASE_OFF],
pdev->tx_throttle.
throttle_time_ms[i][THROTTLE_PHASE_ON]);
}
}
void ol_tx_throttle_init(struct ol_txrx_pdev_t *pdev)
{
uint32_t throttle_period;
uint8_t dutycycle_level[THROTTLE_LEVEL_MAX];
int i;
pdev->tx_throttle.current_throttle_level = THROTTLE_LEVEL_0;
pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF;
qdf_spinlock_create(&pdev->tx_throttle.mutex);
throttle_period = ol_cfg_throttle_period_ms(pdev->ctrl_pdev);
for (i = 0; i < THROTTLE_LEVEL_MAX; i++)
dutycycle_level[i] =
ol_cfg_throttle_duty_cycle_level(pdev->ctrl_pdev, i);
ol_tx_throttle_init_period(cds_get_context(QDF_MODULE_ID_SOC), pdev->id,
throttle_period, &dutycycle_level[0]);
qdf_timer_init(pdev->osdev, &pdev->tx_throttle.phase_timer,
ol_tx_pdev_throttle_phase_timer, pdev,
QDF_TIMER_TYPE_SW);
#ifdef QCA_LL_LEGACY_TX_FLOW_CONTROL
qdf_timer_init(pdev->osdev, &pdev->tx_throttle.tx_timer,
ol_tx_pdev_throttle_tx_timer, pdev, QDF_TIMER_TYPE_SW);
#endif
pdev->tx_throttle.tx_threshold = THROTTLE_TX_THRESHOLD;
}
void
ol_txrx_throttle_pause(ol_txrx_pdev_handle pdev)
{
qdf_spin_lock_bh(&pdev->tx_throttle.mutex);
if (pdev->tx_throttle.is_paused) {
qdf_spin_unlock_bh(&pdev->tx_throttle.mutex);
return;
}
pdev->tx_throttle.is_paused = true;
qdf_spin_unlock_bh(&pdev->tx_throttle.mutex);
ol_txrx_pdev_pause(pdev, 0);
}
void
ol_txrx_throttle_unpause(ol_txrx_pdev_handle pdev)
{
qdf_spin_lock_bh(&pdev->tx_throttle.mutex);
if (!pdev->tx_throttle.is_paused) {
qdf_spin_unlock_bh(&pdev->tx_throttle.mutex);
return;
}
pdev->tx_throttle.is_paused = false;
qdf_spin_unlock_bh(&pdev->tx_throttle.mutex);
ol_txrx_pdev_unpause(pdev, 0);
}