summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Peng <robinpeng@google.com>2022-11-14 04:55:41 +0000
committerRobin Peng <robinpeng@google.com>2022-11-14 04:55:41 +0000
commitc242902003d6a5479ddb12b5f8685ceb2ddc7324 (patch)
tree9539c91fb25c0a764c450175d6d39d4556865047
parent6c9312a5b819594e65624f69b61588279a47a082 (diff)
parent8f70f3b22b29e0de3ab13478ab08c842ee221d7a (diff)
downloadwlan_ptracker-c242902003d6a5479ddb12b5f8685ceb2ddc7324.tar.gz
Bug: 255246572 Change-Id: I7e12d9b01fe646cca8623f9ff87aa4b8941f5135 Signed-off-by: Robin Peng <robinpeng@google.com>
-rw-r--r--BUILD.bazel20
-rw-r--r--Kconfig16
-rw-r--r--Makefile32
-rw-r--r--core.h36
-rw-r--r--debug.h37
-rw-r--r--debugfs.c248
-rw-r--r--debugfs.h75
-rw-r--r--dynamic_twt_manager.c870
-rw-r--r--dynamic_twt_manager.h149
-rw-r--r--main.c117
-rw-r--r--notifier.c105
-rw-r--r--notifier.h33
-rw-r--r--scenes_fsm.c351
-rw-r--r--scenes_fsm.h78
-rw-r--r--tp_monitor.c228
-rw-r--r--tp_monitor.h48
-rw-r--r--wlan_ptracker_client.h42
17 files changed, 2485 insertions, 0 deletions
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 0000000..2f49773
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,20 @@
+# NOTE: THIS FILE IS EXPERIMENTAL FOR THE BAZEL MIGRATION AND NOT USED FOR
+# YOUR BUILDS CURRENTLY.
+#
+# It is not yet the source of truth for your build. If you're looking to modify
+# the build file, modify the Android.bp file instead. Do *not* modify this file
+# unless you have coordinated with the team managing the Soong to Bazel
+# migration.
+
+load("//build/kleaf:kernel.bzl", "kernel_module")
+
+kernel_module(
+ name = "wlan_ptracker.cloudripper",
+ outs = [
+ "wlan_ptracker.ko",
+ ],
+ kernel_build = "//private/gs-google:cloudripper",
+ visibility = [
+ "//private/gs-google:__pkg__",
+ ],
+)
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..24d6608
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# WiFi Performance Tracker Driver
+#
+
+config WLAN_PTRACKER
+ bool "WiFi Performance Tracker Driver"
+ help
+ WiFi Performance Tracker support.
+ default y
+
+config DYNAMIC_TWT_SUPPORT
+ bool "Dynamic TWT Setup Support "
+ help
+ WiFi Performance Tracker support dynamic TWT setup.
+ default y
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7e3342f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for WiFi performance tracker driver
+#
+
+obj-$(CONFIG_WLAN_PTRACKER) += wlan_ptracker.o
+
+# common
+wlan_ptracker-$(CONFIG_WLAN_PTRACKER) += main.o tp_monitor.o
+wlan_ptracker-$(CONFIG_WLAN_PTRACKER) += notifier.o
+wlan_ptracker-$(CONFIG_WLAN_PTRACKER) += scenes_fsm.o
+
+# debugfs
+wlan_ptracker-$(CONFIG_DEBUG_FS) += debugfs.o
+
+# dynamic twt setup
+wlan_ptracker-$(CONFIG_DYNAMIC_TWT_SETUP) += dynamic_twt_manager.o
+
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+ifeq ($(CONFIG_WLAN_PTRACKER),)
+KBUILD_OPTIONS += CONFIG_WLAN_PTRACKER=m
+KBUILD_OPTIONS += CONFIG_DYNAMIC_TWT_SETUP=y
+endif
+
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/wlan/wlan_ptracker
+
+ccflags-y := $(EXTRA_CFLAGS)
+
+modules modules_install clean:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) $(KBUILD_OPTIONS) W=1 $(@)
diff --git a/core.h b/core.h
new file mode 100644
index 0000000..8c43e98
--- /dev/null
+++ b/core.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#ifndef _WLAN_PTRACKER_CORE_H
+#define _WLAN_PTRACKER_CORE_H
+
+#include "debugfs.h"
+#include "debug.h"
+#include "tp_monitor.h"
+#include "notifier.h"
+#include "scenes_fsm.h"
+#include "wlan_ptracker_client.h"
+#include "dynamic_twt_manager.h"
+
+#define DSCP_MASK 0xfc
+#define DSCP_MAX (DSCP_MASK + 1)
+#define DSCP_SHIFT 2
+#define DSCP_MAP_MAX 10
+
+struct wlan_ptracker_core {
+ struct device device;
+ struct tp_monitor_stats tp;
+ struct wlan_ptracker_notifier notifier;
+ struct wlan_ptracker_debugfs debugfs;
+ struct wlan_ptracker_fsm fsm;
+ struct net_device *dev;
+ struct dytwt_manager *dytwt;
+ struct wlan_ptracker_client *client;
+ u8 dscp_to_ac[DSCP_MAX];
+};
+#endif /* _WLAN_PTRACKER_CORE_H */
diff --git a/debug.h b/debug.h
new file mode 100644
index 0000000..fd44e7d
--- /dev/null
+++ b/debug.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#ifndef _WLAN_PTRACKER_DEBUG_H
+#define _WLAN_PTRACKER_DEBUG_H
+
+#define PTRACKER_PREFIX "wlan_ptracker"
+
+#define ptracker_err(core, fmt, ...) \
+ do { \
+ dev_err(&core->device, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define ptracker_info(core, fmt, ...) \
+ do { \
+ dev_info(&core->device, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define ptracker_dbg(core, fmt, ...) \
+ do { \
+ dev_dbg(&core->device, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#ifdef TP_DEBUG
+#define tp_info(tp, fmt, ...) \
+ do { \
+ if ((tp)->debug && (tp)->dev) \
+ dev_info(tp->dev->dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+#else
+#define tp_info(tp, fmt, ...)
+#endif
+
+#endif /* _WLAN_PTRACKER_DEBUG_H */
diff --git a/debugfs.c b/debugfs.c
new file mode 100644
index 0000000..6c52393
--- /dev/null
+++ b/debugfs.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for WiFi Performance Tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/timekeeping.h>
+#include <linux/rtc.h>
+#include "core.h"
+#include "debugfs.h"
+
+static const char *const state2str[WLAN_SCENE_MAX] = {
+ "Idle", "Web", "Youtube", "Low latency", "Throughput"
+};
+
+#define READ_BUF_SIZE 1024
+static ssize_t action_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ char *buf;
+ int len = 0;
+ int i;
+ ssize_t ret;
+
+ buf = vmalloc(READ_BUF_SIZE);
+
+ if (!buf)
+ return 0;
+
+ len += scnprintf(buf + len, READ_BUF_SIZE - len,
+ "==== DSCP to AC mapping table ===\n");
+ for (i = 0 ; i < DSCP_MAX; i++) {
+ if (!core->dscp_to_ac[i])
+ continue;
+ len += scnprintf(buf + len, READ_BUF_SIZE - len,
+ "dscp[%d] : %u\n", i, core->dscp_to_ac[i]);
+ }
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static void update_dscp(struct wlan_ptracker_core *core, u32 dscp, u32 ac)
+{
+ ptracker_info(core, "dscp %d, ac: %d\n", dscp, ac);
+ if (dscp > DSCP_MASK)
+ return;
+ if (ac > WMM_AC_VO)
+ return;
+
+ core->dscp_to_ac[dscp] = ac;
+}
+
+static ssize_t action_write(struct file *file,
+ const char __user *buf, size_t len, loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ struct wlan_ptracker_debugfs *debugfs = &core->debugfs;
+ u32 action;
+
+ if (kstrtouint_from_user(buf, len, 10, &action))
+ return -EFAULT;
+
+ /* active action */
+ switch (action) {
+ case ACTION_DSCP_UPDATE:
+ update_dscp(core, debugfs->dscp, debugfs->ac);
+ break;
+ default:
+ ptracker_err(core, "action %d is not supported!\n", action);
+ return -ENOTSUPP;
+ }
+ return len;
+}
+
+static const struct file_operations dscp_ops = {
+ .open = simple_open,
+ .read = action_read,
+ .write = action_write,
+ .llseek = generic_file_llseek,
+};
+
+static ssize_t ptracker_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ struct wlan_ptracker_debugfs *debugfs = container_of(kobj, struct wlan_ptracker_debugfs,
+ kobj);
+ struct ptracker_kobj_attr *ptracker_attr = container_of(attr, struct ptracker_kobj_attr,
+ attr);
+ int ret = -EIO;
+
+ if (ptracker_attr->show)
+ ret = ptracker_attr->show(debugfs, buf);
+ return ret;
+}
+
+static ssize_t ptracker_sysfs_store(struct kobject *kobj, struct attribute *attr, const char *buf,
+ size_t count)
+{
+ struct wlan_ptracker_debugfs *debugfs =
+ container_of(kobj, struct wlan_ptracker_debugfs, kobj);
+ struct ptracker_kobj_attr *ptracker_attr =
+ container_of(attr, struct ptracker_kobj_attr, attr);
+ int ret = -EIO;
+
+ if (ptracker_attr->store)
+ ret = ptracker_attr->store(debugfs, buf, count);
+ return ret;
+}
+
+static struct sysfs_ops ptracker_sysfs_ops = {
+ .show = ptracker_sysfs_show,
+ .store = ptracker_sysfs_store,
+};
+
+static struct kobj_type ptracker_ktype = {
+ .sysfs_ops = &ptracker_sysfs_ops,
+};
+
+static int wlan_ptracker_sysfs_init(struct wlan_ptracker_debugfs *debugfs)
+{
+ int ret;
+
+ ret = kobject_init_and_add(&debugfs->kobj, &ptracker_ktype, NULL, PTRACKER_PREFIX);
+ if (ret)
+ kobject_put(&debugfs->kobj);
+ return ret;
+}
+
+static void wlan_ptracker_sysfs_exit(struct wlan_ptracker_debugfs *debugfs)
+{
+ kobject_del(&debugfs->kobj);
+ kobject_put(&debugfs->kobj);
+}
+
+int wlan_ptracker_debugfs_init(struct wlan_ptracker_debugfs *debugfs)
+{
+ struct wlan_ptracker_core *core = container_of(
+ debugfs, struct wlan_ptracker_core, debugfs);
+
+ debugfs->root = debugfs_create_dir(PTRACKER_PREFIX, NULL);
+ if (!debugfs->root)
+ return -ENODEV;
+ debugfs_create_file("action", 0600, debugfs->root, core, &dscp_ops);
+ debugfs_create_u32("dscp", 0600, debugfs->root, &debugfs->dscp);
+ debugfs_create_u32("ac", 0600, debugfs->root, &debugfs->ac);
+ wlan_ptracker_sysfs_init(debugfs);
+ return 0;
+}
+
+void wlan_ptracker_debugfs_exit(struct wlan_ptracker_debugfs *debugfs)
+{
+ debugfs_remove_recursive(debugfs->root);
+ debugfs->root = NULL;
+ wlan_ptracker_sysfs_exit(debugfs);
+}
+
+struct history_manager *wlan_ptracker_history_create(int entry_count, int entry_size)
+{
+ struct history_manager *hm;
+
+ if (entry_count < 0 || entry_size < sizeof(struct history_entry))
+ return NULL;
+
+ hm = kzalloc(sizeof(struct history_manager) + entry_size * entry_count, GFP_KERNEL);
+ if (!hm)
+ return NULL;
+
+ /* initial manager */
+ hm->entry_count = entry_count;
+ hm->entry_size = entry_size;
+ hm->cur = 0;
+ hm->round = 0;
+ mutex_init(&hm->mutex);
+ return hm;
+}
+
+void wlan_ptracker_history_destroy(struct history_manager *hm)
+{
+ if (hm)
+ kfree(hm);
+}
+
+void * wlan_ptracker_history_store(struct history_manager *hm, u32 state)
+{
+ struct history_entry *entry;
+
+ if (!hm->entry_count)
+ return NULL;
+
+ entry = (struct history_entry *)(hm->entries + (hm->cur * hm->entry_size));
+ entry->state = state;
+ entry->valid = true;
+ ktime_get_real_ts64(&entry->ts);
+
+ /* update dytwt history */
+ mutex_lock(&hm->mutex);
+ hm->cur++;
+ if (hm->cur / hm->entry_count)
+ hm->round++;
+ hm->cur %= hm->entry_count;
+ mutex_unlock(&hm->mutex);
+ return entry;
+}
+
+static int history_get_tm(struct history_entry *entry, char *time, size_t len)
+{
+ struct rtc_time tm;
+
+ rtc_time64_to_tm(entry->ts.tv_sec - (sys_tz.tz_minuteswest * 60), &tm);
+ return scnprintf(time, len, "%ptRs", &tm);
+}
+
+size_t wlan_ptracker_history_read(struct wlan_ptracker_core *core, struct history_manager *hm,
+ char *buf, int buf_len)
+{
+ u8 *ptr;
+ struct history_entry *cur, *next;
+ int len = 0;
+ int i, j;
+
+ len += scnprintf(buf + len, buf_len - len,
+ "==== %s History ===\n", hm->name);
+ len += scnprintf(buf + len, buf_len - len,
+ "round: %d, cur: %d, entry len: %d, size: %d\n",
+ hm->round, hm->cur, hm->entry_count, hm->entry_size);
+
+ ptr = hm->entries;
+ for (i = 0 ; i < hm->entry_count; i++) {
+ cur = (struct history_entry *) ptr;
+ if (!cur->valid)
+ break;
+ j = (i + 1) % hm->entry_count;
+ next = (struct history_entry *)(hm->entries + (j * hm->entry_size));
+ len += scnprintf(buf + len, buf_len - len, "%02d: ", i);
+ len += history_get_tm(cur, buf + len, buf_len - len);
+ len += scnprintf(buf + len, buf_len - len, "%12s =>", state2str[cur->state]);
+ if (hm->priv_read)
+ len += hm->priv_read(core, cur, next, buf + len, buf_len - len);
+ len += scnprintf(buf + len, buf_len - len, "\n");
+ ptr += hm->entry_size;
+ }
+ return len;
+}
diff --git a/debugfs.h b/debugfs.h
new file mode 100644
index 0000000..7b2e282
--- /dev/null
+++ b/debugfs.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+
+#ifndef _WLAN_PTRACKER_DEBUGFS_H
+#define _WLAN_PTRACKER_DEBUGFS_H
+
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/time64.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+
+struct wlan_ptracker_core;
+
+struct wlan_ptracker_debugfs {
+ struct dentry *root;
+ struct kobject kobj;
+ u32 dscp;
+ u32 ac;
+ u32 action;
+ u32 log_level;
+};
+
+struct ptracker_kobj_attr {
+ struct attribute attr;
+ ssize_t (*show)(struct wlan_ptracker_debugfs *, char *);
+ ssize_t (*store)(struct wlan_ptracker_debugfs *, const char *, size_t count);
+};
+
+enum {
+ FEATURE_FLAG_TWT,
+ FEATURE_FLAG_MAX
+};
+
+enum {
+ ACTION_DSCP_UPDATE,
+ ACTION_MAX,
+};
+
+struct scene_statistic {
+ u64 awake;
+ u64 asleep;
+};
+
+struct history_entry {
+ u32 state;
+ bool valid;
+ struct timespec64 ts;
+};
+
+#define MODULE_NAME_MAX 64
+struct history_manager {
+ char name[MODULE_NAME_MAX];
+ int cur;
+ int round;
+ int entry_count;
+ int entry_size;
+ struct mutex mutex;
+ int (*priv_read)(struct wlan_ptracker_core *core, void *cur, void *next, char *buf, int len);
+ u8 entries[0];
+};
+
+extern int wlan_ptracker_debugfs_init(struct wlan_ptracker_debugfs *debugfs);
+extern void wlan_ptracker_debugfs_exit(struct wlan_ptracker_debugfs *debugfs);
+extern struct history_manager *wlan_ptracker_history_create(int entry_count, int entry_size);
+extern void wlan_ptracker_history_destroy(struct history_manager *hm);
+extern void *wlan_ptracker_history_store(struct history_manager *hm, u32 state);
+extern size_t wlan_ptracker_history_read(struct wlan_ptracker_core *core,
+ struct history_manager *hm, char *buf, int len);
+
+#endif /* _WLAN_PTRACKER_DEBUGFS_H */
diff --git a/dynamic_twt_manager.c b/dynamic_twt_manager.c
new file mode 100644
index 0000000..1701a57
--- /dev/null
+++ b/dynamic_twt_manager.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include <linux/debugfs.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include "core.h"
+
+#define DYMAIC_TWT_CONFIG_ID 3
+
+/* for tcp one pair case */
+#define TWT_IDLE_INTERVAL (500 * 1024) /* 512000 */
+#define TWT_IDLE_DURATION (768 * 32) /* 24576 */
+#define TWT_WEB_INTERVAL (104 * 1024) /* 106496 */
+#define TWT_WEB_DURATION (256 * 32) /* 8192 */
+#define TWT_YOUTUBE_INTERVAL (10 * 1024) /* 10240 */
+#define TWT_YOUTUBE_DURATION (256 * 32) /* 8192 */
+
+/* define reason*/
+enum {
+ TWT_SETUP_REASON_FRAMEWORK = WLAN_PTRACKER_NOTIFY_MAX,
+ TWT_SETUP_REASON_FORCE,
+ TWT_SETUP_REASON_RUNTIME,
+ TWT_SETUP_REASON_MAX,
+};
+
+static const char *const reason2str[TWT_SETUP_REASON_MAX] = {
+ "tp", "scene_change", "scene_prep", "suspend", "sta_connect",
+ "sta_discont", "dytwt_enable", "dytwt_disable", "framework",
+ "force", "runtime",
+};
+
+static const char *const state2str[WLAN_SCENE_MAX] = {
+ "Idle", "Web", "Youtube", "Low latency", "Throughput"
+};
+
+static struct dytwt_scene_action dytwt_actions[WLAN_SCENE_MAX + 1] = {
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_IDLE_DURATION,
+ .wake_interval = TWT_IDLE_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_WEB_DURATION,
+ .wake_interval = TWT_WEB_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_TEARDOWN,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_YOUTUBE_DURATION,
+ .wake_interval = TWT_YOUTUBE_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_TEARDOWN,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ },
+ },
+ {
+ .action = TWT_ACTION_TEARDOWN,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ },
+ },
+ /* used for force mode */
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_IDLE_DURATION,
+ .wake_interval = TWT_IDLE_INTERVAL,
+ },
+ }
+};
+#define TWT_ACTION_SIZE ARRAY_SIZE(dytwt_actions)
+
+static int dytwt_client_twt_setup(struct wlan_ptracker_client *client, u32 state)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->setup)
+ return -EINVAL;
+
+ if (state >= WLAN_SCENE_MAX)
+ return -EINVAL;
+
+ return client->dytwt_ops->setup(client->priv, &dytwt_actions[state].param);
+}
+
+static int dytwt_client_twt_teardown(struct wlan_ptracker_client *client, u32 state)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->teardown)
+ return -EINVAL;
+
+ if (state >= WLAN_SCENE_MAX)
+ return -EINVAL;
+ return client->dytwt_ops->teardown(client->priv, &dytwt_actions[state].param);
+}
+
+static bool dytwt_client_twt_cap(struct wlan_ptracker_client *client)
+{
+ struct dytwt_cap param;
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+ int ret;
+
+ if (!client->dytwt_ops || !client->priv)
+ return false;
+
+ if (!client->dytwt_ops->get_cap)
+ return false;
+
+ ret = client->dytwt_ops->get_cap(client->priv, &param);
+
+ ptracker_dbg(core, "%d, %d, %d, %d\n", param.device_cap, param.peer_cap,
+ param.link_speed, param.rssi);
+ if (ret)
+ return false;
+
+ if (!param.peer_cap || !param.device_cap) {
+ ptracker_err(core, "dytwt is not enabled due to capability: %d, %d\n",
+ param.device_cap, param.peer_cap);
+ return false;
+ }
+
+ if (param.rssi != 0 && param.rssi < dytwt->rssi_threshold) {
+ ptracker_err(dytwt->core, "dytwt is not enabled due to rssi %d < %d\n",
+ param.rssi, dytwt->rssi_threshold);
+ return false;
+ }
+
+ if (param.link_speed < dytwt->link_threshold) {
+ ptracker_err(dytwt->core, "dytwt is not enabled due to linkspeed %d < %d\n",
+ param.link_speed, dytwt->link_threshold);
+ return false;
+ }
+ return true;
+}
+
+static int dytwt_client_twt_pwrstates(struct wlan_ptracker_client *client,
+ struct dytwt_pwr_state *state)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_pwrstates)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_pwrstates(client->priv, state);
+}
+
+static int dytwt_client_twt_get_stats(struct wlan_ptracker_client *client,
+ struct dytwt_stats *stats)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_stats)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_stats(client->priv, stats);
+}
+
+static int dytwt_client_twt_get_status(struct wlan_ptracker_client *client,
+ struct dytwt_status *status)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_status)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_status(client->priv, status);
+}
+
+static inline void dytwt_record_get_pwr(u64 asleep, u64 awake, u64 *total, int *percent)
+{
+ /* for percent */
+ *total = (asleep + awake) / 100;
+ *percent = (*total == 0) ? 0 : (asleep / *total);
+ /* trans 100 us to ms */
+ *total /= 10;
+}
+
+static int dytwt_record_priv_read(struct wlan_ptracker_core *core, void *cur, void *next,
+ char *buf, int len)
+{
+ struct dytwt_entry *c = cur;
+ struct dytwt_entry *n = next;
+ int period_percent, total_percent;
+ u64 period_time, total_time;
+ u64 awake, asleep;
+
+ /* next is the current state */
+ if (n->pwr.asleep < c->pwr.asleep) {
+ struct dytwt_pwr_state pwr;
+ dytwt_client_twt_pwrstates(core->client, &pwr);
+ awake = pwr.awake - c->pwr.awake;
+ asleep = pwr.asleep - c->pwr.asleep;
+ /* get total */
+ dytwt_record_get_pwr(pwr.asleep, pwr.awake, &total_time, &total_percent);
+ } else {
+ /* get period */
+ awake = n->pwr.awake - c->pwr.awake;
+ asleep = n->pwr.asleep - c->pwr.asleep;
+ /* get total */
+ dytwt_record_get_pwr(c->pwr.asleep, c->pwr.awake, &total_time, &total_percent);
+ }
+ dytwt_record_get_pwr(asleep, awake, &period_time, &period_percent);
+ return scnprintf(buf, len,
+ "Applied: %s, Time: %llu (%llu) ms, Percent: %d%% (%d%%) Reason: %s, Rate: %d",
+ c->apply ? "TRUE" : "FALSE", period_time, total_time, period_percent, total_percent,
+ reason2str[c->reason], c->rate);
+}
+
+static void dytwt_counter_update(struct dytwt_manager *dytwt, struct dytwt_pwr_state *pwr)
+{
+ struct dytwt_counters *counter = &dytwt->counters;
+ struct dytwt_statistic *stat = &counter->scene[dytwt->prev];
+ u64 asleep = pwr->asleep - counter->prev_asleep;
+ u64 awake = pwr->awake - counter->prev_awake;
+ u64 count = pwr->count - counter->prev_asleep_cnt;
+
+ stat->asleep += asleep;
+ stat->awake += awake;
+ counter->prev_asleep = pwr->asleep;
+ counter->prev_awake = pwr->awake;
+ counter->prev_asleep_cnt = pwr->count;
+ counter->total_awake += awake;
+ counter->total_sleep += asleep;
+ counter->total_sleep_cnt += count;
+}
+
+static void dytwt_mgmt_history_store(struct wlan_ptracker_client *client,
+ struct dytwt_manager *dytwt, struct wlan_scene_event *msg, bool apply, u32 reason)
+{
+ struct dytwt_entry *entry;
+
+ /* record assign base*/
+ entry = wlan_ptracker_history_store(dytwt->hm, msg->dst);
+ if (!entry)
+ return;
+ /* record private values */
+ entry->apply = apply;
+ entry->reason = reason;
+ entry->rate = msg->rate;
+ dytwt_client_twt_pwrstates(client, &entry->pwr);
+ dytwt_counter_update(dytwt, &entry->pwr);
+ /* prev will be used for decided teardown or not. */
+ dytwt->prev = msg->dst;
+}
+
+/* This function is running in thread context */
+static int _dytwt_scene_change_handler(struct dytwt_manager *dytwt,
+ struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct wlan_scene_event *msg = &core->fsm.msg;
+ struct dytwt_scene_action *act;
+ bool apply = false;
+ u32 state = msg->dst;
+ int ret = 0;
+
+ if (!(dytwt->feature_flag & BIT(FEATURE_FLAG_TWT)))
+ goto out;
+
+ if (!dytwt_client_twt_cap(client)) {
+ ptracker_dbg(dytwt->core, "twt is not supported on device or peer\n");
+ goto out;
+ }
+ act = &dytwt_actions[state];
+
+ /* follow action to setup */
+ if (act->action == TWT_ACTION_SETUP)
+ ret = dytwt_client_twt_setup(client, state);
+ apply = ret ? false : true;
+out:
+ /* store record of history even twt is not applied */
+ dytwt_mgmt_history_store(client, dytwt, msg, apply, msg->reason);
+ ptracker_dbg(dytwt->core, "twt setup for state: %d, reason: %s, ret: %d\n",
+ state, reason2str[msg->reason], ret);
+ return ret;
+}
+
+static void dytwt_delay_setup(struct work_struct *work)
+{
+ struct dytwt_manager *dytwt = container_of(work, struct dytwt_manager, setup_wq.work);
+ struct wlan_ptracker_core *core = dytwt->core;
+ struct wlan_ptracker_client *client;
+
+ if (!core)
+ return;
+
+ client = core->client;
+ /* for first time update value is required*/
+ dytwt->twt_cap = dytwt_client_twt_cap(client);
+ _dytwt_scene_change_handler(dytwt, client);
+}
+
+#define TWT_WAIT_STA_READY_TIME 2000
+static int dytwt_scene_change_handler(struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct wlan_scene_event *msg = &core->fsm.msg;
+
+ if (msg->reason == WLAN_PTRACKER_NOTIFY_STA_CONNECT)
+ schedule_delayed_work(&dytwt->setup_wq, msecs_to_jiffies(TWT_WAIT_STA_READY_TIME));
+ else
+ _dytwt_scene_change_handler(dytwt, client);
+ return 0;
+}
+
+#define TWT_HISTORY_BUF_SIZE 10240
+static ssize_t twt_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ struct dytwt_manager *dytwt = core->dytwt;
+ char *buf;
+ int len;
+ ssize_t ret;
+
+ buf = vmalloc(TWT_HISTORY_BUF_SIZE);
+
+ if (!buf)
+ return -ENOMEM;
+
+ len = wlan_ptracker_history_read(core, dytwt->hm, buf, TWT_HISTORY_BUF_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations twt_ops = {
+ .open = simple_open,
+ .read = twt_read,
+ .llseek = generic_file_llseek,
+};
+
+static void dytwt_force_twt_setup(struct wlan_ptracker_client *client, struct dytwt_manager *dytwt,
+ u32 reason)
+{
+ int ret = 0;
+ bool apply = false;
+ u32 state = dytwt->state;
+ struct wlan_scene_event msg;
+ struct dytwt_scene_action *act = &dytwt_actions[state];
+
+ switch(act->action) {
+ case TWT_ACTION_SETUP:
+ ret = dytwt_client_twt_setup(client, state);
+ break;
+ case TWT_ACTION_TEARDOWN:
+ ret = dytwt_client_twt_teardown(client, state);
+ break;
+ default:
+ break;
+ }
+ apply = ret ? false : true;
+ msg.dst = dytwt->state;
+ /* store record of history even twt is not applied */
+ dytwt_mgmt_history_store(client, dytwt, &msg, apply, reason);
+}
+
+static inline void twt_enable(struct wlan_ptracker_client *client, bool enable, u32 reason)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+
+ if (enable) {
+ dytwt->feature_flag |= BIT(FEATURE_FLAG_TWT);
+ dytwt_scene_change_handler(client);
+ } else {
+ dytwt->state = WLAN_SCENE_TPUT;
+ dytwt_force_twt_setup(client, dytwt, reason);
+ dytwt->feature_flag &= ~BIT(FEATURE_FLAG_TWT);
+ }
+}
+
+#define DYTWT_RUNTIME_TIMER 2000
+static void dytwt_runtime(struct work_struct *work)
+{
+ struct dytwt_manager *dytwt = container_of(work, struct dytwt_manager, wq.work);
+ struct dytwt_scene_action *act;
+ struct wlan_ptracker_client *client;
+
+ if (!dytwt->core)
+ goto end;
+
+ if (dytwt->prev == WLAN_SCENE_MAX)
+ goto end;
+
+ client = dytwt->core->client;
+ act = &dytwt_actions[dytwt->prev];
+ /* update twt_cap periodically */
+ dytwt->twt_cap = dytwt_client_twt_cap(client);
+ if (act->action == TWT_ACTION_SETUP && !dytwt->twt_cap) {
+ dytwt->state = WLAN_SCENE_TPUT;
+ ptracker_dbg(dytwt->core, "teardown twt due to hit threshold\n");
+ dytwt_force_twt_setup(client, dytwt, TWT_SETUP_REASON_RUNTIME);
+ }
+end:
+ schedule_delayed_work(&dytwt->wq, msecs_to_jiffies(DYTWT_RUNTIME_TIMER));
+}
+
+static void update_twt_flag(struct wlan_ptracker_core *core, struct dytwt_manager *dytwt)
+{
+ twt_enable(core->client, !(dytwt->feature_flag & BIT(FEATURE_FLAG_TWT)),
+ TWT_SETUP_REASON_FORCE);
+}
+
+static void update_twt_parameters(struct dytwt_manager *dytwt)
+{
+ u32 state = dytwt->state;
+ struct dytwt_scene_action *cfg_act = &dytwt_actions[WLAN_SCENE_MAX];
+ struct dytwt_scene_action *cur_act = &dytwt_actions[state];
+
+ cur_act->param.wake_duration = cfg_act->param.wake_duration;
+ cur_act->param.wake_interval = cfg_act->param.wake_interval;
+ cur_act->action = cfg_act->action;
+}
+
+static void dytwt_stats_dump(struct wlan_ptracker_client *client, struct dytwt_manager *dytwt)
+{
+ struct dytwt_stats stats;
+ struct wlan_ptracker_core *core = dytwt->core;
+
+ stats.config_id = DYMAIC_TWT_CONFIG_ID;
+ dytwt_client_twt_get_stats(client, &stats);
+
+ ptracker_info(core, "rx_ucast_pkts: %d, rx_pkts_retried: %d\n",
+ stats.rx_ucast_pkts, stats.rx_pkts_retried);
+ ptracker_info(core, "rx_pkt_sz_avg: %d, rx_pkts_avg: %d\n",
+ stats.rx_pkt_sz_avg, stats.rx_pkts_avg);
+ ptracker_info(core, "rx_pkts_min: %d, rx_pkts_max: %d\n",
+ stats.rx_pkts_min, stats.rx_pkts_max);
+ ptracker_info(core, "tx_ucast_pkts: %d, tx_failures: %d\n",
+ stats.tx_ucast_pkts, stats.tx_failures);
+ ptracker_info(core, "tx_pkt_sz_avg: %d, tx_pkts_avg: %d\n",
+ stats.tx_pkt_sz_avg, stats.tx_pkts_avg);
+ ptracker_info(core, "tx_pkts_min: %d, tx_pkts_max: %d\n",
+ stats.tx_pkts_min, stats.tx_pkts_max);
+ ptracker_info(core, "sp_seq: %d, eosp_count: %d, eosp_dur_avg: %d\n",
+ stats.sp_seq, stats.eosp_count, stats.eosp_dur_avg);
+}
+
+static void dytwt_status_dump(struct wlan_ptracker_client *client, struct dytwt_manager *dytwt)
+{
+ struct dytwt_status status;
+
+ status.config_id = DYMAIC_TWT_CONFIG_ID;
+ dytwt_client_twt_get_status(client, &status);
+
+ ptracker_info(dytwt->core, "config_id: %d, flow_id: %d, flow_flags: %x\n",
+ status.config_id, status.flow_id, status.flow_flags);
+ ptracker_info(dytwt->core, "setup_cmd: %d, channel: %d, nego_type: %d\n",
+ status.setup_cmd, status.channel, status.nego_type);
+ ptracker_info(dytwt->core, "wake_dur: %d, wake_int: %d\n",
+ status.wake_dur, status.wake_int);
+}
+
+static int dytwt_debugfs_action(struct wlan_ptracker_core *core, u32 action)
+{
+ struct dytwt_pwr_state pwr_state;
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct wlan_ptracker_client *client = core->client;
+
+ switch (action) {
+ case TWT_TEST_FORCE_STATE:
+ dytwt_force_twt_setup(client, dytwt, TWT_SETUP_REASON_FORCE);
+ break;
+ case TWT_TEST_CAP:
+ dytwt_client_twt_cap(client);
+ break;
+ case TWT_TEST_PWRSTATS:
+ dytwt_client_twt_pwrstates(client, &pwr_state);
+ break;
+ case TWT_TEST_ONOFF:
+ update_twt_flag(core, dytwt);
+ break;
+ case TWT_TEST_SET_PARAM:
+ update_twt_parameters(dytwt);
+ break;
+ case TWT_TEST_DUMP_STATS:
+ dytwt_stats_dump(client, dytwt);
+ break;
+ case TWT_TEST_DUMP_STATUS:
+ dytwt_status_dump(client, dytwt);
+ break;
+ default:
+ ptracker_err(core, "action %d is not supported\n", action);
+ return -ENOTSUPP;
+ }
+ return 0;
+}
+
+static ssize_t twt_params_write(struct file *file, const char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ u32 action;
+
+ if (kstrtouint_from_user(buf, len, 10, &action))
+ return -EFAULT;
+
+ dytwt_debugfs_action(core, action);
+ return len;
+}
+
+static int dytwt_params_read(char *buf, int len)
+{
+ struct dytwt_scene_action *act;
+ struct dytwt_setup_param *param;
+ int count = 0;
+ int i;
+
+ count += scnprintf(buf + count, len - count,
+ "===================\n");
+ for (i = 0 ; i < TWT_ACTION_SIZE; i++) {
+ act = &dytwt_actions[i];
+ param = &act->param;
+ count += scnprintf(buf + count, len - count,
+ "state: %d, action: %d\n", i, act->action);
+ count += scnprintf(buf + count, len - count,
+ "config_id: %d, nego_type: %d\n",
+ param->config_id, param->nego_type);
+ count += scnprintf(buf + count, len - count,
+ "wake_interval: %u\n", param->wake_interval);
+ count += scnprintf(buf + count, len - count,
+ "wake_duration: %u\n", param->wake_duration);
+ count += scnprintf(buf + count, len - count,
+ "===================\n");
+ }
+ return count;
+}
+
+#define TWT_PARAM_BUF_SIZE 1024
+static ssize_t twt_params_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos)
+{
+ char *buf;
+ int len;
+ int ret;
+
+ buf = vmalloc(TWT_PARAM_BUF_SIZE);
+ if (!buf)
+ return -ENOMEM;
+ len = dytwt_params_read(buf, TWT_PARAM_BUF_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations twt_params_ops = {
+ .open = simple_open,
+ .read = twt_params_read,
+ .write = twt_params_write,
+ .llseek = generic_file_llseek,
+};
+
+static int dytwt_statistic_read(struct wlan_ptracker_core *core, char *buf, int len)
+{
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct dytwt_counters *counter = &dytwt->counters;
+ struct dytwt_statistic *ds;
+ struct dytwt_pwr_state pwr;
+ int buf_count = 0;
+ int i, percent;
+ u64 total, awake, asleep, count;
+
+ buf_count += scnprintf(buf + buf_count, len - buf_count,
+ "==== Dynamic TWT Setup Statistics ===\n");
+ dytwt_client_twt_pwrstates(dytwt->core->client, &pwr);
+ for (i = 0 ; i < WLAN_SCENE_MAX; i++) {
+ ds = &counter->scene[i];
+ awake = ds->awake;
+ asleep = ds->asleep;
+ if (i == dytwt->prev) {
+ awake += pwr.awake - counter->prev_awake;
+ asleep += pwr.asleep - counter->prev_asleep;
+ }
+ dytwt_record_get_pwr(asleep, awake, &total, &percent);
+ buf_count += scnprintf(buf + buf_count, len - buf_count,
+ "%s, total: %llu, awake: %llu, asleep: %llu (%d%%)\n", state2str[i], total,
+ awake / 1000, asleep / 1000, percent);
+ }
+
+ awake = counter->total_awake + pwr.awake - counter->prev_awake;
+ asleep = counter->total_sleep + pwr.asleep - counter->prev_asleep;
+ count = counter->total_sleep_cnt + pwr.count - counter->prev_asleep_cnt;
+ dytwt_record_get_pwr(asleep, awake, &total, &percent);
+ buf_count += scnprintf(buf + buf_count, len - buf_count,
+ "All, total: %llu, awake: %llu, asleep: %llu (%d%%), sleep cnt: %llu\n",
+ total, awake / 1000, asleep / 1000, percent, count);
+ return buf_count;
+}
+
+#define TWT_STATISTIC_SIZE 512
+static ssize_t twt_statistic_read(struct file *file, char __user *userbuf, size_t count,
+ loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ char *buf;
+ int len;
+ int ret;
+
+ buf = vmalloc(TWT_STATISTIC_SIZE);
+ if (!buf)
+ return -ENOMEM;
+
+ len = dytwt_statistic_read(core, buf, TWT_STATISTIC_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations twt_statistic_ops = {
+ .open = simple_open,
+ .read = twt_statistic_read,
+ .llseek = generic_file_llseek,
+};
+
+static void dytwt_scene_change_prepare_handler(struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+ u32 prev_state = dytwt->prev;
+
+ if (!(dytwt->feature_flag & BIT(FEATURE_FLAG_TWT)))
+ return;
+
+ /*
+ * prepare to change state, teardown the original setup first.
+ * This change is not recorded in history.
+ */
+ if (dytwt_actions[prev_state].action == TWT_ACTION_SETUP)
+ dytwt_client_twt_teardown(client, dytwt->prev);
+}
+
+static int dytwt_notifier_handler(struct notifier_block *nb, unsigned long event, void *ptr)
+{
+ struct wlan_ptracker_core *core = ptr;
+ struct wlan_ptracker_client *client = core->client;
+ struct dytwt_manager *dytwt = core->dytwt;
+
+ switch (event) {
+ case WLAN_PTRACKER_NOTIFY_SCENE_CHANGE:
+ dytwt_scene_change_handler(client);
+ break;
+ case WLAN_PTRACKER_NOTIFY_SCENE_CHANGE_PREPARE:
+ dytwt_scene_change_prepare_handler(client);
+ break;
+ case WLAN_PTRACKER_NOTIFY_STA_CONNECT:
+ schedule_delayed_work(&dytwt->wq, msecs_to_jiffies(DYTWT_RUNTIME_TIMER));
+ break;
+ case WLAN_PTRACKER_NOTIFY_STA_DISCONNECT:
+ cancel_delayed_work_sync(&dytwt->wq);
+ break;
+ case WLAN_PTRACKER_NOTIFY_DYTWT_ENABLE:
+ twt_enable(client, true, TWT_SETUP_REASON_FRAMEWORK);
+ break;
+ case WLAN_PTRACKER_NOTIFY_DYTWT_DISABLE:
+ twt_enable(client, false, TWT_SETUP_REASON_FRAMEWORK);
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static ssize_t dytwt_dumpstate_statistic(struct dytwt_manager *dytwt, char *buf)
+{
+ return dytwt_statistic_read(dytwt->core, buf, PAGE_SIZE);
+}
+
+static ssize_t dytwt_dumpstate_history(struct dytwt_manager *dytwt, char *buf)
+{
+ return wlan_ptracker_history_read(dytwt->core, dytwt->hm, buf, PAGE_SIZE);
+}
+
+static struct dytwt_kobj_attr attr_twt_history =
+ __ATTR(history, 0664, dytwt_dumpstate_history, NULL);
+
+static struct dytwt_kobj_attr attr_twt_statistic =
+ __ATTR(statistic, 0664, dytwt_dumpstate_statistic, NULL);
+
+static struct attribute *default_file_attrs[] = {
+ &attr_twt_history.attr,
+ &attr_twt_statistic.attr,
+ NULL,
+};
+
+static ssize_t dytwt_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ struct dytwt_manager *dytwt;
+ struct dytwt_kobj_attr *dytwt_attr;
+ int ret = -EIO;
+
+ dytwt = container_of(kobj, struct dytwt_manager, kobj);
+ dytwt_attr = container_of(attr, struct dytwt_kobj_attr, attr);
+
+ if (dytwt_attr->show)
+ ret = dytwt_attr->show(dytwt, buf);
+ return ret;
+}
+
+static ssize_t dytwt_sysfs_store(struct kobject *kobj, struct attribute *attr, const char *buf,
+ size_t count)
+{
+ struct dytwt_manager *dytwt;
+ struct dytwt_kobj_attr *dytwt_attr;
+ int ret = -EIO;
+
+ dytwt = container_of(kobj, struct dytwt_manager, kobj);
+ dytwt_attr = container_of(attr, struct dytwt_kobj_attr, attr);
+
+ if (dytwt_attr->show)
+ ret = dytwt_attr->store(dytwt, buf, count);
+ return ret;
+
+}
+
+static struct sysfs_ops dytwt_sysfs_ops = {
+ .show = dytwt_sysfs_show,
+ .store = dytwt_sysfs_store,
+};
+
+static struct kobj_type dytwt_ktype = {
+ .sysfs_ops = &dytwt_sysfs_ops,
+ .default_attrs = default_file_attrs,
+};
+
+static int dytwt_sysfs_init(struct dytwt_manager *dytwt, struct wlan_ptracker_debugfs *debugfs)
+{
+ int ret;
+
+ ret = kobject_init_and_add(&dytwt->kobj, &dytwt_ktype, &debugfs->kobj, "twt");
+ if (ret)
+ kobject_put(&dytwt->kobj);
+ return ret;
+}
+
+static void dytwt_sysfs_exit(struct dytwt_manager *dytwt)
+{
+ kobject_del(&dytwt->kobj);
+ kobject_put(&dytwt->kobj);
+}
+
+static int dytwt_debugfs_init(struct wlan_ptracker_core *core)
+{
+ struct wlan_ptracker_debugfs *debugfs = &core->debugfs;
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct dytwt_scene_action *act = &dytwt_actions[WLAN_SCENE_MAX];
+
+ dytwt->feature_flag |= BIT(FEATURE_FLAG_TWT);
+ dytwt->dir = debugfs_create_dir("twt", debugfs->root);
+ if (!dytwt->dir)
+ return -ENODEV;
+
+ debugfs_create_file("history", 0666, dytwt->dir, core, &twt_ops);
+ debugfs_create_file("statistics", 0666, dytwt->dir, core, &twt_statistic_ops);
+ debugfs_create_file("twt_params", 0666, dytwt->dir, core, &twt_params_ops);
+ debugfs_create_u32("state", 0666, dytwt->dir, &dytwt->state);
+ debugfs_create_u32("wake_interval", 0666, dytwt->dir, &act->param.wake_interval);
+ debugfs_create_u32("wake_duration", 0666, dytwt->dir, &act->param.wake_duration);
+ debugfs_create_u32("action", 0666, dytwt->dir, &act->action);
+ debugfs_create_u32("feature_flag", 0666, dytwt->dir, &dytwt->feature_flag);
+ dytwt_sysfs_init(dytwt, debugfs);
+ return 0;
+}
+
+static void dytwt_debugfs_exit(struct dytwt_manager *dytwt)
+{
+ if (dytwt->dir)
+ debugfs_remove_recursive(dytwt->dir);
+ dytwt_sysfs_exit(dytwt);
+}
+
+#define TWT_DEFAULT_MIN_LINK_SPEED (180000)
+#define TWT_DEFAULT_MIN_RSSI (-70)
+#define DYTWT_RECORD_MAX 30
+static struct dytwt_manager *dytwt_mgmt_init(struct wlan_ptracker_core *core)
+{
+ struct history_manager *hm;
+ struct dytwt_manager *dytwt = kzalloc(sizeof(struct dytwt_manager), GFP_KERNEL);
+
+ if (!dytwt)
+ return NULL;
+
+ dytwt->state = WLAN_SCENE_IDLE;
+ dytwt->prev = WLAN_SCENE_MAX;
+ dytwt->core = core;
+ dytwt->link_threshold = TWT_DEFAULT_MIN_LINK_SPEED;
+ dytwt->rssi_threshold = TWT_DEFAULT_MIN_RSSI;
+ INIT_DELAYED_WORK(&dytwt->wq, dytwt_runtime);
+ INIT_DELAYED_WORK(&dytwt->setup_wq, dytwt_delay_setup);
+ hm = wlan_ptracker_history_create(DYTWT_RECORD_MAX, sizeof(struct dytwt_entry));
+ if (!hm) {
+ kfree(dytwt);
+ return NULL;
+ }
+ strncpy(hm->name, "Dynamic TWT Setup", sizeof(hm->name));
+ hm->priv_read = dytwt_record_priv_read;
+ dytwt->hm = hm;
+
+ return dytwt;
+}
+
+static void dytwt_mgmt_exit(struct dytwt_manager *dytwt)
+{
+ cancel_delayed_work_sync(&dytwt->wq);
+ cancel_delayed_work_sync(&dytwt->setup_wq);
+ wlan_ptracker_history_destroy(dytwt->hm);
+ kfree(dytwt);
+}
+
+static struct notifier_block twt_nb = {
+ .priority = 0,
+ .notifier_call = dytwt_notifier_handler,
+};
+
+int dytwt_init(struct wlan_ptracker_core *core)
+{
+ core->dytwt = dytwt_mgmt_init(core);
+ dytwt_debugfs_init(core);
+ return wlan_ptracker_register_notifier(&core->notifier, &twt_nb);
+}
+
+void dytwt_exit(struct wlan_ptracker_core *core)
+{
+ struct dytwt_manager *dytwt = core->dytwt;
+
+ core->dytwt = NULL;
+ wlan_ptracker_unregister_notifier(&core->notifier, &twt_nb);
+
+ if (!dytwt)
+ return;
+
+ dytwt_debugfs_exit(dytwt);
+ dytwt_mgmt_exit(dytwt);
+}
diff --git a/dynamic_twt_manager.h b/dynamic_twt_manager.h
new file mode 100644
index 0000000..495329b
--- /dev/null
+++ b/dynamic_twt_manager.h
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#ifndef __TP_TRACKER_DYNAMIC_TWT_SETUP_H
+#define __TP_TRACKER_DYNAMIC_TWT_SETUP_H
+
+#include "debugfs.h"
+
+struct wlan_ptracker_client;
+struct wlan_ptracker_core;
+
+struct dytwt_setup_param {
+ u8 config_id;
+ u8 nego_type;
+ u8 trigger_type;
+ u32 wake_duration;
+ u32 wake_interval;
+};
+
+struct dytwt_cap {
+ u16 device_cap;
+ u16 peer_cap;
+ u32 rssi;
+ int link_speed;
+};
+
+struct dytwt_pwr_state {
+ u64 awake;
+ u64 asleep;
+ u64 count;
+};
+
+struct dytwt_status {
+ u32 config_id;
+ u32 flow_id;
+ u32 flow_flags;
+ u32 setup_cmd;
+ u32 channel;
+ u32 nego_type;
+ u32 wake_dur;
+ u32 wake_int;
+};
+
+struct dytwt_stats {
+ u32 config_id;
+ u32 sp_seq; /* sequence number of the service period */
+ u32 tx_ucast_pkts; /* Number of unicast Tx packets in TWT SPs */
+ u32 tx_pkts_min; /* Minimum number of Tx packets in a TWT SP */
+ u32 tx_pkts_max; /* Maximum number of Tx packets in a TWT SP */
+ u32 tx_pkts_avg; /* Average number of Tx packets in each TWT SP */
+ u32 tx_failures; /* Tx packets failure count */
+ u32 rx_ucast_pkts; /* Number of unicast Rx packets in TWT SPs */
+ u32 rx_pkts_min; /* Minimum number of Rx packets in a TWT SP */
+ u32 rx_pkts_max; /* Maximum number of Rx packets in a TWT SP */
+ u32 rx_pkts_avg; /* Average number of Rx packets in each TWT SP */
+ u32 rx_pkts_retried; /* retried Rx packets count */
+ u32 tx_pkt_sz_avg; /* Average Tx packet size in TWT SPs */
+ u32 rx_pkt_sz_avg; /* Average Rx Packet size in TWT SPs */
+ u32 eosp_dur_avg; /* Average Wake duration in SPs ended due to EOSP */
+ u32 eosp_count; /* Count of TWT SPs ended due to EOSP */
+};
+
+struct dytwt_client_ops {
+ int (*setup)(void *priv, struct dytwt_setup_param *param);
+ int (*teardown)(void *priv, struct dytwt_setup_param *param);
+ int (*get_cap)(void *priv, struct dytwt_cap *cap);
+ int (*get_pwrstates)(void *priv, struct dytwt_pwr_state *state);
+ int (*get_stats)(void *priv, struct dytwt_stats *stats);
+ int (*get_status)(void *priv, struct dytwt_status *status);
+};
+
+enum {
+ TWT_ACTION_SETUP,
+ TWT_ACTION_TEARDOWN,
+ TWT_ACTION_MAX,
+};
+
+enum {
+ TWT_TEST_FORCE_STATE = 1,
+ TWT_TEST_CAP,
+ TWT_TEST_PWRSTATS,
+ TWT_TEST_ONOFF,
+ TWT_TEST_SET_PARAM,
+ TWT_TEST_DUMP_STATS,
+ TWT_TEST_DUMP_STATUS,
+ TWT_TEST_MAX,
+};
+
+struct dytwt_scene_action {
+ u32 action;
+ struct dytwt_setup_param param;
+};
+
+struct dytwt_entry {
+ /* base should put as first membor */
+ struct history_entry base;
+ bool apply;
+ u32 rate;
+ u32 reason;
+ struct dytwt_pwr_state pwr;
+} __align(void *);
+
+struct dytwt_statistic {
+ u64 awake;
+ u64 asleep;
+};
+
+#define DYTWT_COUNTER_MAX 6
+#define DYTWT_COUNTER_TOTAL 5
+struct dytwt_counters {
+ u64 total_awake;
+ u64 total_sleep;
+ u64 total_sleep_cnt;
+ u64 prev_awake;
+ u64 prev_asleep;
+ u64 prev_asleep_cnt;
+ struct dytwt_statistic scene[DYTWT_COUNTER_MAX];
+};
+
+struct dytwt_manager {
+ u32 prev;
+ u32 feature_flag;
+ u32 state;
+ struct history_manager *hm;
+ u32 rssi_threshold;
+ u32 link_threshold;
+ bool twt_cap;
+ struct delayed_work wq;
+ struct delayed_work setup_wq;
+ struct wlan_ptracker_core *core;
+ struct dytwt_counters counters;
+ struct kobject kobj;
+ struct dentry *dir;
+};
+
+struct dytwt_kobj_attr {
+ struct attribute attr;
+ ssize_t (*show)(struct dytwt_manager *, char *);
+ ssize_t (*store)(struct dytwt_manager *, const char *, size_t count);
+};
+
+extern int dytwt_init(struct wlan_ptracker_core *core);
+extern void dytwt_exit(struct wlan_ptracker_core *core);
+#endif /* __TP_TRACKER_DYNAMIC_TWT_SETUP_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..f0d3ad5
--- /dev/null
+++ b/main.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/net_namespace.h>
+#include "core.h"
+
+#define client_to_core(client) ((struct wlan_ptracker_core *)((client)->core))
+
+/* Default mapping rule follow 802.11e */
+static const int dscp_trans[WMM_AC_MAX][DSCP_MAP_MAX] = {
+ {0, 24, 26, 28, 30, -1}, /* AC_BE */
+ {8, 10, 12, 14, 16, 18, 20, 22, -1}, /* AC_BK */
+ {32, 34, 36, 38, 40, 46, -1}, /* AC_VI */
+ {48, 56, -1}, /* AC_VO */
+};
+
+static void dscp_to_ac_init(u8 *dscp_to_ac)
+{
+ int i, j;
+
+ for (i = 0 ; i < WMM_AC_MAX; i++) {
+ for (j = 0 ; j < DSCP_MAP_MAX; j++) {
+ int dscp = dscp_trans[i][j];
+
+ if (dscp == -1)
+ break;
+ dscp_to_ac[dscp] = i;
+ }
+ }
+}
+
+static struct wlan_ptracker_core *wlan_ptracker_core_init(struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core;
+
+ core = kzalloc(sizeof(struct wlan_ptracker_core), GFP_KERNEL);
+ if (!core)
+ return NULL;
+
+ core->client = client;
+ device_initialize(&core->device);
+ dev_set_name(&core->device, PTRACKER_PREFIX);
+ device_add(&core->device);
+ dscp_to_ac_init(core->dscp_to_ac);
+ wlan_ptracker_debugfs_init(&core->debugfs);
+ wlan_ptracker_notifier_init(&core->notifier);
+ scenes_fsm_init(&core->fsm);
+ dytwt_init(core);
+ return core;
+}
+
+static void wlan_ptracker_core_exit(struct wlan_ptracker_core *core)
+{
+ dytwt_exit(core);
+ scenes_fsm_exit(&core->fsm);
+ wlan_ptracker_notifier_exit(&core->notifier);
+ wlan_ptracker_debugfs_exit(&core->debugfs);
+ device_del(&core->device);
+ kfree(core);
+}
+
+static int client_event_handler(void *priv, u32 event)
+{
+ struct wlan_ptracker_client *client = priv;
+ struct wlan_ptracker_core *core = client_to_core(client);
+
+ return wlan_ptracker_call_chain(&core->notifier, event, core);
+}
+
+int wlan_ptracker_register_client(struct wlan_ptracker_client *client)
+{
+ client->core = wlan_ptracker_core_init(client);
+ if (!client->core)
+ return -ENOMEM;
+ client->cb = client_event_handler;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wlan_ptracker_register_client);
+
+void wlan_ptracker_unregister_client(struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client_to_core(client);
+
+ if (!core)
+ return;
+ client->cb = NULL;
+ client->core = NULL;
+ wlan_ptracker_core_exit(core);
+}
+EXPORT_SYMBOL_GPL(wlan_ptracker_unregister_client);
+
+static int __init wlan_ptracker_init(void)
+{
+ pr_debug("module init: %s\n", PTRACKER_PREFIX);
+ return 0;
+}
+
+static void __exit wlan_ptracker_exit(void)
+{
+ pr_debug("module exit: %s\n", PTRACKER_PREFIX);
+}
+
+module_init(wlan_ptracker_init);
+module_exit(wlan_ptracker_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Star Chang <starchang@google.com>");
+MODULE_DESCRIPTION("WiFi Performance Tracker");
diff --git a/notifier.c b/notifier.c
new file mode 100644
index 0000000..fb950b8
--- /dev/null
+++ b/notifier.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include "core.h"
+
+#define notifier_to_core(notifier) container_of(notifier, struct wlan_ptracker_core, notifier)
+
+#define nb_to_notifier(nb) container_of(nb, struct wlan_ptracker_notifier, nb)
+
+static int up_event_handler(struct wlan_ptracker_core *core, struct net_device *dev)
+{
+ core->dev = dev;
+ dev_hold(dev);
+ core->client->priv = dev;
+ return tp_monitor_init(&core->tp);
+}
+
+static void down_event_handler(struct wlan_ptracker_core *core)
+{
+ struct net_device *dev = core->dev;
+ tp_monitor_exit(&core->tp);
+ core->dev = NULL;
+ core->client->priv = NULL;
+ if (dev)
+ dev_put(dev);
+}
+
+static int netdevice_notifier_handler(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+ struct wlan_ptracker_notifier *notifier = nb_to_notifier(nb);
+ struct wlan_ptracker_core *core = notifier_to_core(notifier);
+
+ if (!core->client)
+ return NOTIFY_DONE;
+
+ if (strcmp(netdev->name, core->client->ifname))
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_UP:
+ ptracker_info(core, "interface up (%s)\n", netdev->name);
+ up_event_handler(core, netdev);
+ break;
+ case NETDEV_DOWN:
+ ptracker_info(core, "interface down (%s)\n", netdev->name);
+ down_event_handler(core);
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+int wlan_ptracker_register_notifier(struct wlan_ptracker_notifier *notifier,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&notifier->notifier_head, nb);
+}
+
+void wlan_ptracker_unregister_notifier(struct wlan_ptracker_notifier *notifier,
+ struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&notifier->notifier_head, nb);
+}
+
+int wlan_ptracker_call_chain(struct wlan_ptracker_notifier *notifier,
+ unsigned long event, void *priv)
+{
+ struct wlan_ptracker_core *core = priv;
+ int ret;
+
+ ret = blocking_notifier_call_chain(&notifier->notifier_head, event, priv);
+ if (ret & NOTIFY_STOP_MASK)
+ ptracker_err(core, "notifier chain fail with status %#x\n", ret);
+
+ return notifier_to_errno(ret);
+}
+
+void wlan_ptracker_notifier_init(struct wlan_ptracker_notifier *notifier)
+{
+ notifier->prev_event = jiffies;
+ /* register to device notifier */
+ notifier->nb.priority = 0;
+ notifier->nb.notifier_call = netdevice_notifier_handler;
+ register_netdevice_notifier(&notifier->nb);
+ /* init notifier chain to notify plugin modules */
+ BLOCKING_INIT_NOTIFIER_HEAD(&notifier->notifier_head);
+}
+
+void wlan_ptracker_notifier_exit(struct wlan_ptracker_notifier *notifier)
+{
+ /* reset notifier */
+ BLOCKING_INIT_NOTIFIER_HEAD(&notifier->notifier_head);
+ /* unregister netdevice notifier*/
+ unregister_netdevice_notifier(&notifier->nb);
+ notifier->prev_event = 0;
+}
+
diff --git a/notifier.h b/notifier.h
new file mode 100644
index 0000000..348ab92
--- /dev/null
+++ b/notifier.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#ifndef __TP_TRACKER_NOTIFIER_H
+#define __TP_TRACKER_NOTIFIER_H
+
+#include <linux/notifier.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+
+struct wlan_ptracker_notifier {
+ struct notifier_block nb;
+ unsigned long prev_event;
+ struct blocking_notifier_head notifier_head;
+};
+
+extern void wlan_ptracker_notifier_init(struct wlan_ptracker_notifier *nb);
+extern void wlan_ptracker_notifier_exit(struct wlan_ptracker_notifier *nb);
+
+extern int wlan_ptracker_register_notifier(struct wlan_ptracker_notifier *notifier,
+ struct notifier_block *nb);
+extern void wlan_ptracker_unregister_notifier(struct wlan_ptracker_notifier *notifier,
+ struct notifier_block *nb);
+extern int wlan_ptracker_call_chain(struct wlan_ptracker_notifier *notifier,
+ unsigned long event, void *priv);
+
+#endif /* __TP_TRACKER_NOTIFIER_H */
diff --git a/scenes_fsm.c b/scenes_fsm.c
new file mode 100644
index 0000000..622e13f
--- /dev/null
+++ b/scenes_fsm.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include <linux/debugfs.h>
+#include "core.h"
+
+#define fsm_to_core(fsm) (container_of(fsm, struct wlan_ptracker_core, fsm))
+
+static struct wlan_state_condition conditions[FSM_STATE_MAX] = {
+ {
+ .scene = WLAN_SCENE_IDLE,
+ .ac_mask = WMM_AC_ALL_MASK,
+ .min_tp_threshold = 0,
+ .max_tp_threshold = 1000,
+ },
+ {
+ .scene = WLAN_SCENE_WEB,
+ .ac_mask = WMM_AC_ALL_MASK,
+ .min_tp_threshold = 1000,
+ .max_tp_threshold = 9000,
+ },
+ {
+ .scene = WLAN_SCENE_YOUTUBE,
+ .ac_mask = WMM_AC_ALL_MASK,
+ .min_tp_threshold = 9000,
+ .max_tp_threshold = 60000,
+ },
+ {
+ .scene = WLAN_SCENE_LOW_LATENCY,
+ .ac_mask = BIT(WMM_AC_VO),
+ /* VO >= 1 Mbps */
+ .min_tp_threshold = 1000,
+ .max_tp_threshold = __INT_MAX__,
+ },
+ {
+ .scene = WLAN_SCENE_TPUT,
+ .ac_mask = WMM_AC_ALL_MASK,
+ .min_tp_threshold = 60000,
+ .max_tp_threshold = __INT_MAX__,
+ },
+};
+
+static int fsm_thread(void *param)
+{
+ struct wlan_ptracker_fsm *fsm = param;
+ struct wlan_scene_event *msg = &fsm->msg;
+ struct wlan_ptracker_core *core = fsm_to_core(fsm);
+
+ while (fsm->thread_run) {
+ if (kthread_should_stop()) {
+ ptracker_info(core, "kthread is stopped\n");
+ break;
+ }
+ wait_for_completion(&fsm->event);
+
+ ptracker_dbg(core, "state: %d, trans state %d -> %d, rate %llu\n",
+ msg->state, msg->src, msg->dst, msg->rate);
+ wlan_ptracker_call_chain(&core->notifier, WLAN_PTRACKER_NOTIFY_SCENE_CHANGE_PREPARE, core);
+ wlan_ptracker_call_chain(&core->notifier, WLAN_PTRACKER_NOTIFY_SCENE_CHANGE, core);
+ msg->state = msg->dst;
+ }
+ return 0;
+}
+
+static bool scenes_check(u64 rate, const struct wlan_state_condition *cond,
+ struct wlan_scene_event *msg)
+{
+ /* change bits rate to Kbits rate */
+ u64 krate = rate / 1000;
+
+ if (krate >= cond->min_tp_threshold && krate < cond->max_tp_threshold) {
+ msg->rate = rate;
+ return true;
+ }
+ return false;
+}
+
+static u32 scenes_condition_get(struct wlan_ptracker_fsm *fsm)
+{
+ const struct wlan_state_condition *cond;
+ struct wlan_ptracker_core *core = fsm_to_core(fsm);
+ struct tp_monitor_stats *stats = &core->tp;
+ struct wlan_scene_event *msg = &fsm->msg;
+ int i, j;
+
+ /* check from higher restriction to lower */
+ for (i = FSM_STATE_MAX - 1 ; i >= 0 ; i--) {
+ cond = &fsm->conditions[i];
+ if (cond->ac_mask == WMM_AC_ALL_MASK) {
+ if (scenes_check(
+ stats->tx[WMM_AC_MAX].rate + stats->rx[WMM_AC_MAX].rate,
+ cond, msg))
+ return cond->scene;
+ } else {
+ u64 total_tx = 0;
+ u64 total_rx = 0;
+
+ for (j = 0 ; j < WMM_AC_MAX; j++) {
+ if (cond->ac_mask & BIT(j)) {
+ total_tx += stats->tx[j].rate;
+ total_rx += stats->rx[j].rate;
+ }
+ if (scenes_check(total_tx + total_rx, cond, msg))
+ return cond->scene;
+ }
+ }
+ }
+ return fsm->msg.state;
+}
+
+/* TODO: fine-tune period threshold */
+#define RESET_THRESHOLD 1
+static void scenes_fsm_decision(struct wlan_ptracker_core *core, u32 type)
+{
+ struct wlan_ptracker_fsm *fsm = &core->fsm;
+ struct wlan_scene_event *msg = &fsm->msg;
+ u32 new_state;
+ bool except = false;
+
+ if (!fsm->fsm_thread)
+ return;
+
+ /* condition check */
+ new_state = scenes_condition_get(fsm);
+
+ /* reset check */
+ if (type == WLAN_PTRACKER_NOTIFY_SUSPEND) {
+ fsm->reset_cnt++;
+ except = (fsm->reset_cnt >= RESET_THRESHOLD) ? true : false;
+ }
+
+ /* check state isn't change and not first time do nothing */
+ if (new_state == msg->state &&
+ type != WLAN_PTRACKER_NOTIFY_STA_CONNECT)
+ return;
+ /* new state must higher then current state */
+ if (new_state < msg->state && !except) {
+ ptracker_dbg(core,
+ "state not change since new state %d < old state %d and reset_cnt is %d\n",
+ new_state, msg->state, fsm->reset_cnt);
+ return;
+ }
+
+ ptracker_dbg(core, "type %d, reset_cnt %d, %d -> %d\n",
+ type, fsm->reset_cnt, msg->state, new_state);
+
+ /* clear reset cnt*/
+ fsm->reset_cnt = 0;
+ /* decide to trans state */
+ mutex_lock(&msg->lock);
+ msg->src = msg->state;
+ msg->dst = new_state;
+ msg->reason = type;
+ mutex_unlock(&msg->lock);
+
+ /* send complete to wake up thread to handle fsm */
+ complete(&fsm->event);
+}
+
+static int scene_notifier_handler(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct wlan_ptracker_core *core = ptr;
+ struct wlan_ptracker_notifier *notifier = &core->notifier;
+
+ /*
+ * Events of suspen and sta change will block wlan driver
+ * should not spend too much time. Move complex part to thread handle.
+ */
+ switch (event) {
+ case WLAN_PTRACKER_NOTIFY_SUSPEND:
+#ifdef TP_DEBUG
+ ptracker_dbg(core, "update time (%d)\n",
+ jiffies_to_msecs(jiffies - notifier->prev_event));
+#endif
+ notifier->prev_event = jiffies;
+ case WLAN_PTRACKER_NOTIFY_STA_CONNECT:
+ case WLAN_PTRACKER_NOTIFY_TP:
+ scenes_fsm_decision(core, event);
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block scene_nb = {
+ .priority = 0,
+ .notifier_call = scene_notifier_handler,
+};
+
+static int scene_cond_set(struct wlan_ptracker_fsm *fsm)
+{
+ struct wlan_state_condition *param = &conditions[fsm->state];
+
+ param->ac_mask = fsm->ac_mask;
+ param->max_tp_threshold = fsm->max_tput;
+ param->min_tp_threshold = fsm->min_tput;
+ return 0;
+}
+
+static int scene_debugfs_action(struct wlan_ptracker_core *core, u32 action)
+{
+ struct wlan_ptracker_fsm *fsm = &core->fsm;
+ switch (action) {
+ case SCENE_TEST_SET_PARAM:
+ scene_cond_set(fsm);
+ break;
+ default:
+ ptracker_err(core, "action %d is not supported\n", action);
+ break;
+ }
+ return 0;
+}
+
+static ssize_t scene_params_write(struct file *file,
+ const char __user *buf, size_t len, loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ u32 action;
+
+ if (kstrtouint_from_user(buf, len, 10, &action))
+ return -EFAULT;
+
+ /* active action */
+ scene_debugfs_action(core, action);
+ return 0;
+}
+
+static int _scene_params_read(char *buf, int len)
+{
+ struct wlan_state_condition *param;
+ int count = 0;
+ int i;
+
+ count += scnprintf(buf + count, len - count,
+ "===================\n");
+ for (i = 0 ; i < FSM_STATE_MAX; i++) {
+ param = &conditions[i];
+ count += scnprintf(buf + count, len - count,
+ "state: %d, ac_mask: %#0X\n", i, param->ac_mask);
+ count += scnprintf(buf + count, len - count,
+ "min_tp_threshold: %u\n", param->min_tp_threshold);
+ count += scnprintf(buf + count, len - count,
+ "max_tp_threshold: %u\n", param->max_tp_threshold);
+ count += scnprintf(buf + count, len - count,
+ "===================\n");
+ }
+ return count;
+}
+
+#define SCENE_PARAM_BUF_SIZE 1024
+static ssize_t scene_params_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ char *buf;
+ int len;
+ int ret;
+
+ buf = vmalloc(SCENE_PARAM_BUF_SIZE);
+ if (!buf)
+ return -ENOMEM;
+ len = _scene_params_read(buf, SCENE_PARAM_BUF_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations scene_params_ops = {
+ .open = simple_open,
+ .read = scene_params_read,
+ .write = scene_params_write,
+ .llseek = generic_file_llseek,
+};
+
+static int scene_debugfs_init(struct wlan_ptracker_core *core)
+{
+ struct wlan_ptracker_debugfs *debugfs = &core->debugfs;
+ struct wlan_ptracker_fsm *fsm = &core->fsm;
+
+ fsm->dir = debugfs_create_dir("scene", debugfs->root);
+ if (!fsm->dir)
+ return -ENODEV;
+
+ debugfs_create_file("scene_params", 0600, fsm->dir, core, &scene_params_ops);
+ debugfs_create_u32("state", 0600, fsm->dir, &fsm->state);
+ debugfs_create_u32("min_tput", 0600, fsm->dir, &fsm->min_tput);
+ debugfs_create_u32("max_tput", 0600, fsm->dir, &fsm->max_tput);
+ debugfs_create_u32("ac_mask", 0600, fsm->dir, &fsm->ac_mask);
+ return 0;
+}
+
+int scenes_fsm_init(struct wlan_ptracker_fsm *fsm)
+{
+ struct wlan_scene_event *msg = &fsm->msg;
+ struct wlan_ptracker_core *core = fsm_to_core(fsm);
+ int ret = 0;
+
+ /* assign scenes and conditions */
+ fsm->conditions = &conditions[0];
+ fsm->reset_cnt = 0;
+ /* init msg for receiving event */
+ msg->dst = WLAN_SCENE_IDLE;
+ msg->src = WLAN_SCENE_IDLE;
+ msg->state = WLAN_SCENE_IDLE;
+ mutex_init(&msg->lock);
+ scene_debugfs_init(core);
+
+ /*scene event notifier handler from client */
+ ret = wlan_ptracker_register_notifier(&core->notifier, &scene_nb);
+ if (ret)
+ return ret;
+
+ /* initial thread for listening event */
+ init_completion(&fsm->event);
+ fsm->fsm_thread = kthread_create(fsm_thread, fsm, "wlan_ptracker_thread");
+ if (IS_ERR(fsm->fsm_thread)) {
+ ret = PTR_ERR(fsm->fsm_thread);
+ fsm->fsm_thread = NULL;
+ ptracker_err(core, "unable to start kernel thread %d\n", ret);
+ return ret;
+ }
+ fsm->thread_run = true;
+ wake_up_process(fsm->fsm_thread);
+ return 0;
+}
+
+void scenes_fsm_exit(struct wlan_ptracker_fsm *fsm)
+{
+ struct wlan_ptracker_core *core = fsm_to_core(fsm);
+
+ if (fsm->dir)
+ debugfs_remove_recursive(fsm->dir);
+
+ wlan_ptracker_unregister_notifier(&core->notifier, &scene_nb);
+ fsm->thread_run = false;
+ complete(&fsm->event);
+ if (fsm->fsm_thread) {
+ int ret = kthread_stop(fsm->fsm_thread);
+ fsm->fsm_thread = NULL;
+ if (ret)
+ ptracker_err(core, "stop thread fail: %d\n", ret);
+ }
+ fsm->conditions = NULL;
+ fsm->reset_cnt = 0;
+}
diff --git a/scenes_fsm.h b/scenes_fsm.h
new file mode 100644
index 0000000..e7aa284
--- /dev/null
+++ b/scenes_fsm.h
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#ifndef __WLAN_SCENES_FSM_H
+#define __WLAN_SCENES_FSM_H
+
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/completion.h>
+
+struct wlan_ptracker_core;
+
+enum {
+ WLAN_SCENE_IDLE,
+ WLAN_SCENE_WEB,
+ WLAN_SCENE_YOUTUBE,
+ WLAN_SCENE_LOW_LATENCY,
+ WLAN_SCENE_TPUT,
+ WLAN_SCENE_MAX,
+};
+
+/* follow design spec to define the conditions */
+enum {
+ FSM_STATE_C0,
+ FSM_STATE_C1,
+ FSM_STATE_C2,
+ FSM_STATE_C3,
+ FSM_STATE_C4,
+ FSM_STATE_MAX
+};
+
+enum {
+ SCENE_TEST_SET_PARAM,
+ SCENE_TEST_MAX,
+};
+
+struct wlan_state_condition {
+ u32 scene;
+ u32 ac_mask;
+ /* Kbits */
+ u32 min_tp_threshold;
+ u32 max_tp_threshold;
+};
+
+#define WMM_AC_ALL_MASK 0xf
+
+struct wlan_scene_event {
+ struct mutex lock;
+ u32 state;
+ u32 src;
+ u32 dst;
+ u32 reason;
+ u64 rate;
+};
+
+struct wlan_ptracker_fsm {
+ int reset_cnt;
+ bool thread_run;
+ struct completion event;
+ struct wlan_scene_event msg;
+ struct task_struct *fsm_thread;
+ const struct wlan_state_condition *conditions;
+ /* debug usage */
+ struct dentry *dir;
+ u32 state;
+ u32 min_tput;
+ u32 max_tput;
+ u32 ac_mask;
+};
+
+extern int scenes_fsm_init(struct wlan_ptracker_fsm *fsm);
+extern void scenes_fsm_exit(struct wlan_ptracker_fsm *fsm);
+#endif /* __WLAN_SCENES_FSM_H */
diff --git a/tp_monitor.c b/tp_monitor.c
new file mode 100644
index 0000000..3937506
--- /dev/null
+++ b/tp_monitor.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/debugfs.h>
+#include <net/dsfield.h>
+#include "core.h"
+
+#define tp_to_core(_tp) container_of(_tp, struct wlan_ptracker_core, tp)
+
+static void tp_rate_pps_update(struct tp_monitor_counts *counts)
+{
+ unsigned long cur_cnt, cur_bytes;
+ struct tp_monitor_counts *count;
+ int i;
+
+ for (i = 0 ; i < TPM_SIZE_MAX; i++) {
+ count = &counts[i];
+ cur_bytes = count->packet_bytes;
+ cur_cnt = count->packet_cnt;
+ count->rate = (cur_bytes - count->pre_packet_bytes) << 3;
+ count->pps = cur_cnt - count->pre_packet_cnt;
+ count->pre_packet_cnt = cur_cnt;
+ count->pre_packet_bytes = cur_bytes;
+#ifdef TP_DEBUG
+ count->max_packet_bytes = max(count->max_packet_bytes, count->packet_bytes);
+ count->max_packet_cnt = max(count->max_packet_cnt, count->packet_cnt);
+ count->max_pps = max(count->max_pps, count->pps);
+ count->max_rate = max(count->max_rate, count->rate);
+#endif /* TP_DEBUG */
+ }
+}
+
+/* TODO: fine-tune period */
+#define TPM_TIMER_PERIOD 1000
+static void tp_timer_callback(struct timer_list *t)
+{
+ struct tp_monitor_stats *stats = from_timer(stats, t, tp_timer);
+ struct wlan_ptracker_core *core = tp_to_core(stats);
+
+ /* update tx */
+ tp_rate_pps_update(stats->tx);
+ /* update rx */
+ tp_rate_pps_update(stats->rx);
+ mod_timer(t, jiffies + msecs_to_jiffies(TPM_TIMER_PERIOD));
+ /* adjust scenes */
+ wlan_ptracker_call_chain(&core->notifier, WLAN_PTRACKER_NOTIFY_TP, core);
+}
+
+static inline void tp_timer_start(struct tp_monitor_stats *stats)
+{
+ /* update rate per second */
+ timer_setup(&stats->tp_timer, tp_timer_callback, 0);
+ mod_timer(&stats->tp_timer, jiffies + msecs_to_jiffies(TPM_TIMER_PERIOD));
+}
+
+static inline void tp_timer_stop(struct tp_monitor_stats *stats)
+{
+ del_timer_sync(&stats->tp_timer);
+}
+
+static void tp_update_counter(struct wlan_ptracker_core *core,
+ struct tp_monitor_counts *counts, u8 dscp, struct sk_buff *skb)
+{
+ u8 wmm_ac = core->dscp_to_ac[dscp];
+
+ /* update total counters */
+ counts[WMM_AC_MAX].packet_cnt++;
+ counts[WMM_AC_MAX].packet_bytes += skb->len;
+ /* update ac counters */
+ counts[wmm_ac].packet_cnt++;
+ counts[wmm_ac].packet_bytes += skb->len;
+}
+
+static u32 tp_monitor_nf_input(void *priv, struct sk_buff *skb,
+ const struct nf_hook_state *state)
+{
+ struct wlan_ptracker_core *core = priv;
+ struct net_device *dev = skb->dev;
+ u8 dscp;
+
+ if (dev != core->dev)
+ goto out;
+
+ dscp = ip_hdr(skb)->version == 4 ?
+ ipv4_get_dsfield(ip_hdr(skb)) >> DSCP_SHIFT :
+ ipv6_get_dsfield(ipv6_hdr(skb)) >> DSCP_SHIFT;
+
+ tp_info(&core->tp, "rx packets %s, dscp: %d, ip.ver: %d, len: %d, %d\n",
+ dev->name, dscp, ip_hdr(skb)->version, skb->len, skb->data_len);
+ tp_update_counter(core, core->tp.rx, dscp, skb);
+out:
+ return NF_ACCEPT;
+}
+
+static u32 tp_monitor_nf_output(void *priv, struct sk_buff *skb,
+ const struct nf_hook_state *state)
+{
+ struct wlan_ptracker_core *core = priv;
+ struct net_device *dev = skb->dev;
+ u8 dscp;
+
+ if (dev != core->dev)
+ goto out;
+
+ dscp = ip_hdr(skb)->version == 4 ?
+ ipv4_get_dsfield(ip_hdr(skb)) >> DSCP_SHIFT :
+ ipv6_get_dsfield(ipv6_hdr(skb)) >> DSCP_SHIFT;
+
+ tp_info(&core->tp, "tx packets %s, dscp:%d, ip.ver: %d, len: %d\n",
+ dev->name, dscp, ip_hdr(skb)->version, skb->data_len);
+ tp_update_counter(core, core->tp.tx, dscp, skb);
+out:
+ return NF_ACCEPT;
+}
+
+static struct nf_hook_ops wlan_ptracker_nfops[] = {
+ {
+ .hook = tp_monitor_nf_input,
+ .pf = NFPROTO_INET,
+ .hooknum = NF_INET_PRE_ROUTING,
+ .priority = INT_MAX,
+ },
+ {
+ .hook = tp_monitor_nf_output,
+ .pf = NFPROTO_INET,
+ .hooknum = NF_INET_POST_ROUTING,
+ .priority = INT_MAX,
+ },
+};
+#define WLAN_PTRACKER_NF_LEN ARRAY_SIZE(wlan_ptracker_nfops)
+
+static int tp_show(struct seq_file *s, void *unused)
+{
+ struct tp_monitor_counts *counter, *counters = s->private;
+ int i;
+
+ for (i = 0 ; i < TPM_SIZE_MAX; i++) {
+ counter = &counters[i];
+ if (i < WMM_AC_MAX)
+ seq_printf(s, "AC %d ->\n", i);
+ else
+ seq_puts(s, "Total ->\n");
+ seq_printf(s, "packet_cnt : %llu (%llu)\n",
+ counter->packet_cnt, counter->max_packet_cnt);
+ seq_printf(s, "packet_bytes : %llu (%llu)\n",
+ counter->packet_bytes, counter->max_packet_bytes);
+ seq_printf(s, "rate (Kbits) : %llu (%llu)\n",
+ counter->rate / 1000, counter->max_rate / 1000);
+ seq_printf(s, "pps : %llu (%llu)\n",
+ counter->pps, counter->pps);
+ }
+ return 0;
+}
+
+static int counters_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tp_show, inode->i_private);
+}
+
+static const struct file_operations counter_ops = {
+ .open = counters_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int tp_monitor_debugfs_init(struct wlan_ptracker_core *core)
+{
+ struct wlan_ptracker_debugfs *debugfs = &core->debugfs;
+ struct tp_monitor_stats *stats = &core->tp;
+ struct wlan_ptracker_client *client = core->client;
+
+ stats->dir = debugfs_create_dir(client->ifname, debugfs->root);
+ if (!stats->dir)
+ return -ENODEV;
+
+ debugfs_create_u32("log_level", 0600, stats->dir, &stats->debug);
+ debugfs_create_file("tx", 0400, stats->dir, &stats->tx, &counter_ops);
+ debugfs_create_file("rx", 0400, stats->dir, &stats->rx, &counter_ops);
+ return 0;
+}
+
+int tp_monitor_init(struct tp_monitor_stats *stats)
+{
+ struct wlan_ptracker_core *core = tp_to_core(stats);
+ struct net *net = dev_net(core->dev);
+ int err = 0;
+ int i;
+
+ /* debugfs */
+ tp_monitor_debugfs_init(core);
+ /* assign net_device for ingress check and filter */
+ for (i = 0 ; i < WLAN_PTRACKER_NF_LEN; i++) {
+ wlan_ptracker_nfops[i].dev = core->dev;
+ wlan_ptracker_nfops[i].priv = core;
+ }
+
+ /* register hook function to netfilter */
+ err = nf_register_net_hooks(net, wlan_ptracker_nfops, WLAN_PTRACKER_NF_LEN);
+ if (err)
+ goto out;
+
+ /* start a timer to update rate and pps */
+ tp_timer_start(stats);
+ return 0;
+out:
+ ptracker_err(core, "initial err (%d)\n", err);
+ return err;
+}
+
+void tp_monitor_exit(struct tp_monitor_stats *stats)
+{
+ struct wlan_ptracker_core *core = tp_to_core(stats);
+ struct net *net = dev_net(core->dev);
+
+ if (stats->dir)
+ debugfs_remove_recursive(stats->dir);
+ tp_timer_stop(stats);
+ nf_unregister_net_hooks(net, wlan_ptracker_nfops, WLAN_PTRACKER_NF_LEN);
+}
+
diff --git a/tp_monitor.h b/tp_monitor.h
new file mode 100644
index 0000000..1694099
--- /dev/null
+++ b/tp_monitor.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+
+#ifndef __WLAN_TP_MONITOR_H
+#define __WLAN_TP_MONITOR_H
+
+#include <linux/timer.h>
+
+enum {
+ WMM_AC_BE,
+ WMM_AC_BK,
+ WMM_AC_VI,
+ WMM_AC_VO,
+ WMM_AC_MAX
+};
+
+#define TPM_SIZE_MAX (WMM_AC_MAX + 1)
+
+struct tp_monitor_counts {
+ u64 packet_cnt;
+ u64 packet_bytes;
+ u64 pre_packet_bytes;
+ u64 pre_packet_cnt;
+ u64 rate;
+ u64 pps;
+ u64 max_pps;
+ u64 max_packet_cnt;
+ u64 max_packet_bytes;
+ u64 max_rate;
+};
+
+struct tp_monitor_stats {
+ struct tp_monitor_counts tx[TPM_SIZE_MAX];
+ struct tp_monitor_counts rx[TPM_SIZE_MAX];
+ struct timer_list tp_timer;
+ struct dentry *dir;
+ u32 debug;
+};
+
+extern int tp_monitor_init(struct tp_monitor_stats *stats);
+extern void tp_monitor_exit(struct tp_monitor_stats *stats);
+#endif /* __WLAN_TP_MONITOR_H */
diff --git a/wlan_ptracker_client.h b/wlan_ptracker_client.h
new file mode 100644
index 0000000..c4cc3aa
--- /dev/null
+++ b/wlan_ptracker_client.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#ifndef __WLAN_PTRACKER_CLIENT_H
+#define __WLAN_PTRACKER_CLIENT_H
+
+#include "dynamic_twt_manager.h"
+
+#define IFNAME_MAX 16
+
+enum {
+ WLAN_PTRACKER_NOTIFY_TP,
+ WLAN_PTRACKER_NOTIFY_SCENE_CHANGE,
+ WLAN_PTRACKER_NOTIFY_SCENE_CHANGE_PREPARE,
+ WLAN_PTRACKER_NOTIFY_SUSPEND,
+ WLAN_PTRACKER_NOTIFY_STA_CONNECT,
+ WLAN_PTRACKER_NOTIFY_STA_DISCONNECT,
+ WLAN_PTRACKER_NOTIFY_DYTWT_ENABLE,
+ WLAN_PTRACKER_NOTIFY_DYTWT_DISABLE,
+ WLAN_PTRACKER_NOTIFY_MAX,
+};
+
+/* backword compatible */
+#define WLAN_PTRACKER_NOTIFY_SUSPEN WLAN_PTRACKER_NOTIFY_SUSPEND
+
+struct wlan_ptracker_client {
+ void *priv;
+ void *core;
+ char ifname[IFNAME_MAX];
+ struct dytwt_client_ops *dytwt_ops;
+ int (*cb)(void *priv, u32 event);
+};
+
+extern int wlan_ptracker_register_client(struct wlan_ptracker_client *client);
+extern void wlan_ptracker_unregister_client(struct wlan_ptracker_client *client);
+
+#endif /*__WLAN_PTRACKER_CLIENT_H*/