net: wireless: bcmdhd: boost wifi performance
Michael Hsu [Thu, 20 Aug 2015 19:34:13 +0000 (12:34 -0700)]
Calculate network throughput and boost clock frequencies if data
rate exceeds configured threshold.

Bug 1602374
Bug 1653975

Change-Id: I3a786c60910ea7553b5accd109bb3d3ef823c00b
Signed-off-by: Michael Hsu <mhsu@nvidia.com>
Reviewed-on: http://git-master/r/786751
(cherry picked from commit e8117840cf96b10c97dbe417e9f073e872afb986)
Reviewed-on: http://git-master/r/811790
GVS: Gerrit_Virtual_Submit
Reviewed-by: Ashutosh Jha <ajha@nvidia.com>

drivers/net/wireless/bcmdhd/Kconfig
drivers/net/wireless/bcmdhd/Makefile
drivers/net/wireless/bcmdhd/dhd_custom_net_perf_tegra.c [new file with mode: 0644]
drivers/net/wireless/bcmdhd/dhd_linux.c
drivers/net/wireless/bcmdhd/include/dhd_custom_net_perf_tegra.h [new file with mode: 0644]

index 0436a10..c27f772 100644 (file)
@@ -91,3 +91,12 @@ config BCMDHD_CUSTOM_SYSFS_TEGRA
        default y
        ---help---
          Enable custom sysfs for Tegra platform.
+
+config BCMDHD_CUSTOM_NET_PERF_TEGRA
+       bool "Custom network performance boost for Tegra platform"
+       depends on BCMDHD
+       default y
+       ---help---
+         Enable custom network performance boost for Tegra platform.
+         Dynamically boosts clock frequencies based on network traffic
+         requirements.
index 595804f..a0d4c75 100644 (file)
@@ -258,6 +258,10 @@ ifneq ($(CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA),)
   DHDOFILES += dhd_custom_sysfs_tegra_tcpdump.o
 endif
 
+ifneq ($(CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA),)
+  DHDOFILES += dhd_custom_net_perf_tegra.o
+endif
+
 bcmdhd-objs := $(DHDOFILES)
 obj-$(DRIVER_TYPE)   += bcmdhd.o
 
diff --git a/drivers/net/wireless/bcmdhd/dhd_custom_net_perf_tegra.c b/drivers/net/wireless/bcmdhd/dhd_custom_net_perf_tegra.c
new file mode 100644 (file)
index 0000000..ada6d10
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * drivers/net/wireless/bcmdhd/dhd_custom_net_perf_tegra.c
+ *
+ * NVIDIA Tegra Network Performance Boost for BCMDHD driver
+ *
+ * Copyright (C) 2015 NVIDIA Corporation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "dhd_custom_net_perf_tegra.h"
+
+static DEFINE_SEMAPHORE(wifi_sclk_lock);
+static struct clk *wifi_sclk;
+static int wifi_sclk_count;
+
+static void wifi_sclk_enable(void)
+{
+       if (!wifi_sclk)
+               return;
+
+       down(&wifi_sclk_lock);
+       if (++wifi_sclk_count == 1) {
+               pr_debug("%s\n", __func__);
+               clk_enable(wifi_sclk);
+       }
+       up(&wifi_sclk_lock);
+}
+
+static void wifi_sclk_disable(void)
+{
+       if (!wifi_sclk)
+               return;
+
+       down(&wifi_sclk_lock);
+       if (--wifi_sclk_count == 0) {
+               pr_debug("%s\n", __func__);
+               clk_disable(wifi_sclk);
+       }
+       up(&wifi_sclk_lock);
+}
+
+/* network performance policy worker function */
+static void tegra_net_perf_policy_worker(struct work_struct *work)
+{
+       struct delayed_work *dwork
+               = container_of(work, struct delayed_work, work);
+       struct tegra_net_perf_policy *policy
+               = container_of(dwork, struct tegra_net_perf_policy, dwork);
+       unsigned long now, timeout, flags;
+
+       /* lock net perf policy */
+       spin_lock_irqsave(&policy->lock, flags);
+
+       /* get boost timeout */
+       now = jiffies;
+       timeout = policy->jiffies_boost_timeout;
+
+       /* check boost timeout */
+       if (time_before(now, timeout)) {
+               /* boost freq */
+               if (!policy->freq_boost_flag) {
+                       pr_debug("%s: begin freq boost (policy %p)...\n",
+                               __func__, policy);
+                       /* set freq boost flag */
+                       policy->freq_boost_flag = 1;
+                       /* reschedule later to restore freq */
+                       schedule_delayed_work(dwork, timeout - now);
+                       /* unlock net perf policy */
+                       spin_unlock_irqrestore(&policy->lock, flags);
+                       /* boost freq (by enabling wifi sclk) */
+                       wifi_sclk_enable();
+                       return;
+               }
+       } else {
+               /* restore freq */
+               if (policy->freq_boost_flag) {
+                       pr_debug("%s: end freq boost... (policy %p)\n",
+                               __func__, policy);
+                       /* clear freq boost flag */
+                       policy->freq_boost_flag = 0;
+                       /* unlock net perf policy */
+                       spin_unlock_irqrestore(&policy->lock, flags);
+                       /* restore freq (by disabling wifi sclk) */
+                       wifi_sclk_disable();
+                       return;
+               }
+       }
+
+       /* unlock net perf policy */
+       spin_unlock_irqrestore(&policy->lock, flags);
+}
+
+/* network performance policy */
+static struct tegra_net_perf_policy rx_net_perf_policy = {
+       .bps_boost_threshold = TEGRA_NET_PERF_RX_THRESHOLD,
+       .boost_timeout_ms = TEGRA_NET_PERF_RX_TIMEOUT,
+};
+
+static struct tegra_net_perf_policy tx_net_perf_policy = {
+       .bps_boost_threshold = TEGRA_NET_PERF_TX_THRESHOLD,
+       .boost_timeout_ms = TEGRA_NET_PERF_TX_TIMEOUT,
+};
+
+static void calc_network_rate(struct tegra_net_perf_policy *policy,
+       unsigned int bits)
+{
+       unsigned long now = jiffies;
+       unsigned long flags;
+       unsigned long delta;
+
+       /* check if bps exceeds threshold for boosting freq */
+       spin_lock_irqsave(&policy->lock, flags);
+       if (!policy->jiffies_prev)
+               policy->jiffies_prev = now;
+       if (ULONG_MAX - bits < policy->bits) {
+               policy->jiffies_prev = 0;
+               policy->bits = bits;
+               goto unlock;
+       }
+       policy->bits += bits;
+       delta = now - policy->jiffies_prev;
+       if (delta < msecs_to_jiffies(TEGRA_NET_PERF_MIN_SAMPLE_WINDOW))
+               goto unlock;
+       if ((delta > msecs_to_jiffies(TEGRA_NET_PERF_MAX_SAMPLE_WINDOW)) ||
+               (policy->bits / (delta + 1) > ULONG_MAX / HZ)) {
+               policy->jiffies_prev = 0;
+               policy->bits = bits;
+               goto unlock;
+       }
+       policy->bps = policy->bits / (delta + 1) * HZ;
+       if (policy->bps < policy->bps_boost_threshold)
+               goto unlock;
+       policy->jiffies_boost_timeout = now +
+               msecs_to_jiffies(policy->boost_timeout_ms);
+
+       /* boost freq */
+       if (!policy->freq_boost_flag)
+               schedule_delayed_work(&policy->dwork, msecs_to_jiffies(0));
+       else {
+               cancel_delayed_work(&policy->dwork);
+               schedule_delayed_work(&policy->dwork,
+                       msecs_to_jiffies(policy->boost_timeout_ms));
+       }
+
+unlock:
+       spin_unlock_irqrestore(&policy->lock, flags);
+}
+
+void tegra_net_perf_init(void)
+{
+       /* initialize static variable(s) */
+       wifi_sclk = clk_get_sys("tegra-wifi", "sclk");
+       if (IS_ERR(wifi_sclk)) {
+               pr_err("%s: cannot get wifi sclk\n", __func__);
+               wifi_sclk = NULL;
+       }
+
+       /* initialize wifi sclk rate (will not take effect until clk enable) */
+       if (wifi_sclk)
+               if (clk_set_rate(wifi_sclk, TEGRA_NET_PERF_WIFI_SCLK_FREQ) < 0)
+                       pr_err("%s: cannot set wifi sclk rate %ld\n",
+                               __func__, TEGRA_NET_PERF_WIFI_SCLK_FREQ);
+
+       /* sanity-check configuration values */
+       if (rx_net_perf_policy.boost_timeout_ms <
+               TEGRA_NET_PERF_MIN_SAMPLE_WINDOW)
+               rx_net_perf_policy.boost_timeout_ms =
+                       TEGRA_NET_PERF_MIN_SAMPLE_WINDOW;
+       if (tx_net_perf_policy.boost_timeout_ms <
+               TEGRA_NET_PERF_MIN_SAMPLE_WINDOW)
+               tx_net_perf_policy.boost_timeout_ms =
+                       TEGRA_NET_PERF_MIN_SAMPLE_WINDOW;
+
+       /* initialize network performance policy(s) */
+       INIT_DELAYED_WORK(&rx_net_perf_policy.dwork,
+               tegra_net_perf_policy_worker);
+       INIT_DELAYED_WORK(&tx_net_perf_policy.dwork,
+               tegra_net_perf_policy_worker);
+       spin_lock_init(&rx_net_perf_policy.lock);
+       spin_lock_init(&tx_net_perf_policy.lock);
+}
+
+void tegra_net_perf_exit(void)
+{
+       /* uninitialize network performance policy(s) */
+       cancel_delayed_work_sync(&tx_net_perf_policy.dwork);
+       tx_net_perf_policy.freq_boost_flag = 0;
+       cancel_delayed_work_sync(&rx_net_perf_policy.dwork);
+       rx_net_perf_policy.freq_boost_flag = 0;
+
+       /* uninitialize static variable(s) */
+       if (wifi_sclk) {
+               if (wifi_sclk_count > 0) {
+                       pr_debug("%s: wifi sclk disable\n",
+                               __func__);
+                       wifi_sclk_count = 0;
+                       clk_disable(wifi_sclk);
+               }
+               clk_put(wifi_sclk);
+               wifi_sclk = NULL;
+       }
+}
+
+void tegra_net_perf_rx(struct sk_buff *skb)
+{
+       /* calc rx data rate (and boost freq if threshold exceeded) */
+       calc_network_rate(&rx_net_perf_policy, skb->len * 8);
+}
+
+void tegra_net_perf_tx(struct sk_buff *skb)
+{
+       /* calc tx data rate (and boost freq if threshold exceeded) */
+       calc_network_rate(&tx_net_perf_policy, skb->len * 8);
+}
index 1b04a1e..f79ef7d 100644 (file)
 
 #endif
 
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+#include "dhd_custom_net_perf_tegra.h"
+#endif
+
 #ifdef WLMEDIA_HTSF
 #include <linux/time.h>
 #include <htsf.h>
@@ -2637,6 +2641,10 @@ dhd_start_xmit(struct sk_buff *skb, struct net_device *net)
 
        DHD_TRACE(("%s: Enter\n", __FUNCTION__));
 
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+       tegra_net_perf_tx(skb);
+#endif
+
        DHD_OS_WAKE_LOCK(&dhd->pub);
        DHD_PERIM_LOCK_TRY(DHD_FWDER_UNIT(dhd), TRUE);
 
@@ -3205,6 +3213,9 @@ dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *pktbuf, int numpkt, uint8 chan)
                }
 
                if (in_interrupt()) {
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+                       tegra_net_perf_rx(skb);
+#endif
                        netif_rx(skb);
                } else {
                        if (dhd->rxthread_enabled) {
@@ -3221,6 +3232,9 @@ dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *pktbuf, int numpkt, uint8 chan)
                                 * by netif_rx_ni(), but in earlier kernels, we need
                                 * to do it manually.
                                 */
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+                               tegra_net_perf_rx(skb);
+#endif
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
                                netif_rx_ni(skb);
 #else
@@ -3547,6 +3561,9 @@ dhd_rxf_thread(void *data)
                                void *skbnext = PKTNEXT(pub->osh, skb);
                                PKTSETNEXT(pub->osh, skb, NULL);
 
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+                               tegra_net_perf_rx(skb);
+#endif
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
                                netif_rx_ni(skb);
 #else
@@ -3659,6 +3676,9 @@ dhd_sched_rxf(dhd_pub_t *dhdp, void *skb)
                while (skbp) {
                        void *skbnext = PKTNEXT(dhdp->osh, skbp);
                        PKTSETNEXT(dhdp->osh, skbp, NULL);
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+                       tegra_net_perf_rx(skbp);
+#endif
                        netif_rx_ni(skbp);
                        skbp = skbnext;
                }
@@ -7443,6 +7463,9 @@ dhd_module_cleanup(void)
 static void __exit
 dhd_module_exit(void)
 {
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+       tegra_net_perf_exit();
+#endif
        dhd_module_cleanup();
        unregister_reboot_notifier(&dhd_reboot_notifier);
 }
@@ -7455,6 +7478,10 @@ dhd_module_init(void)
 
        DHD_ERROR(("%s in\n", __FUNCTION__));
 
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+       tegra_net_perf_init();
+#endif
+
        DHD_PERIM_RADIO_INIT();
 
        if (firmware_path[0] != '\0') {
@@ -8078,6 +8105,9 @@ dhd_sendup_log(dhd_pub_t *dhdp, void *data, int data_len)
                skb_pull(skb, ETH_HLEN);
 
                /* Send the packet */
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+               tegra_net_perf_rx(skb);
+#endif
                if (in_interrupt()) {
                        netif_rx(skb);
                } else {
diff --git a/drivers/net/wireless/bcmdhd/include/dhd_custom_net_perf_tegra.h b/drivers/net/wireless/bcmdhd/include/dhd_custom_net_perf_tegra.h
new file mode 100644 (file)
index 0000000..b3c7dba
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * drivers/net/wireless/bcmdhd/include/dhd_custom_net_perf_tegra.h
+ *
+ * NVIDIA Tegra Network Performance Boost for BCMDHD driver
+ *
+ * Copyright (C) 2015 NVIDIA Corporation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _dhd_custom_net_perf_tegra_h_
+#define _dhd_custom_net_perf_tegra_h_
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/skbuff.h>
+#include <linux/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+/* measure network data rate for this time period before boosting freq */
+#ifndef TEGRA_NET_PERF_MIN_SAMPLE_WINDOW
+#define TEGRA_NET_PERF_MIN_SAMPLE_WINDOW       30 /* ms */
+#endif  /* TEGRA_NET_PERF_MIN_SAMPLE_WINDOW */
+
+#ifndef TEGRA_NET_PERF_MAX_SAMPLE_WINDOW
+#define TEGRA_NET_PERF_MAX_SAMPLE_WINDOW       3000 /* ms */
+#endif  /* TEGRA_NET_PERF_MAX_SAMPLE_WINDOW */
+
+/* network data rate threshold for boosting freq */
+#ifndef TEGRA_NET_PERF_RX_THRESHOLD
+#define TEGRA_NET_PERF_RX_THRESHOLD    200000000 /* bps */
+#endif  /* TEGRA_NET_PERF_RX_THRESHOLD */
+
+#ifndef TEGRA_NET_PERF_TX_THRESHOLD
+#define TEGRA_NET_PERF_TX_THRESHOLD    200000000 /* bps */
+#endif  /* TEGRA_NET_PERF_TX_THRESHOLD */
+
+/* how long to keep boosting freq after data rate threshold reached */
+#ifndef TEGRA_NET_PERF_RX_TIMEOUT
+#define TEGRA_NET_PERF_RX_TIMEOUT      50 /* ms */
+#endif  /* TEGRA_NET_PERF_RX_TIMEOUT */
+
+#ifndef TEGRA_NET_PERF_TX_TIMEOUT
+#define TEGRA_NET_PERF_TX_TIMEOUT      50 /* ms */
+#endif  /* TEGRA_NET_PERF_TX_TIMEOUT */
+
+/*
+ * how much frequency boost for wifi sclk
+ * - the wifi.sclk pushes up the APB PCLK, which in turn pushes up HCLK and
+ *   SCLK to 2xPCLK level
+ * - so to set SCLK to X, need to specify (X/2) for the wifi.sclk frequency
+ */
+#ifndef TEGRA_NET_PERF_WIFI_SCLK_FREQ
+#define TEGRA_NET_PERF_WIFI_SCLK_FREQ  (265600000UL / 2) /* Hz */
+#endif  /* TEGRA_NET_PERF_WIFI_SCLK_FREQ */
+
+/**
+ * struct tegra_net_perf_policy - network performance policy
+ * @dwork:                     delayed work object for boosting / unboosting
+ *                             frequencies
+ * @lock:                      locks this structure against concurrent access
+ *                             by network i/o functions and the work object
+ * @freq_boost_flag:           set to non-zero if frequency is boosted (and
+ *                             needs to be unboosted later)
+ * @jiffies_prev:              when calculating the data throughput, the time
+ *                             period is current time (jiffies) minus this
+ *                             value
+ * @jiffies_boost_timeout:     the jiffies timestamp at which frequency boost
+ *                             is supposed to expire
+ * @bits:                      the number of bits (it will be divided by the
+ *                             elapsed time when calculating the bits per sec
+ *                             value)
+ * @bps_boost_threshold:       once bits per sec exceeds this value, the
+ *                             frequency will be boosted
+ * @jiffies_boost_timeout:     duration to keep frequency boosted after data
+ *                             throughput drops below threshold (if data
+ *                             throughput constantly stays above threshold,
+ *                             the the frequency will get boosted
+ *                             indefinitely)
+ *
+ * This structure maintains all the variables required to calculate network
+ * throughput, and to boost frequencies for the duration of high network
+ * traffic.
+ */
+struct tegra_net_perf_policy {
+       struct delayed_work dwork;
+       spinlock_t lock;
+       int freq_boost_flag;
+       unsigned long jiffies_prev;
+       unsigned long jiffies_boost_timeout;
+       unsigned long bits;
+       unsigned long bps;
+       unsigned long bps_boost_threshold;
+       unsigned int boost_timeout_ms;
+};
+
+/* initialization */
+void tegra_net_perf_init(void);
+
+void tegra_net_perf_exit(void);
+
+/* network packet rx/tx notification */
+void tegra_net_perf_rx(struct sk_buff *skb);
+
+void tegra_net_perf_tx(struct sk_buff *skb);
+
+#endif  /* _dhd_custom_net_perf_tegra_h_ */