|
@@ -0,0 +1,308 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2012-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.
|
|
|
+ */
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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_pdev *ppdev, int level)
|
|
|
+{
|
|
|
+ struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
|
|
|
+ int ms = 0;
|
|
|
+
|
|
|
+ if (level >= THROTTLE_LEVEL_MAX) {
|
|
|
+ ol_txrx_dbg(
|
|
|
+ "%s invalid throttle level set %d, ignoring\n",
|
|
|
+ __func__, level);
|
|
|
+ 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_pdev *ppdev, int period,
|
|
|
+ uint8_t *dutycycle_level)
|
|
|
+{
|
|
|
+ struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* 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((struct cdp_pdev *)pdev,
|
|
|
+ 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);
|
|
|
+}
|