summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStar Chang <starchang@google.com>2022-07-18 03:43:57 +0000
committerStar Chang <starchang@google.com>2022-11-01 05:44:42 +0000
commitfaff7aaa5abbd90893469aae842c74c9474ea3f4 (patch)
tree6b2176e2436cf6099a967347086777c2958b6fb9
parent6c9312a5b819594e65624f69b61588279a47a082 (diff)
downloadwlan_ptracker-faff7aaa5abbd90893469aae842c74c9474ea3f4.tar.gz
wlan_ptracker: Initial version of driver.
1. support tp monitor 2. support event notifier 3. support scenes fsm 4. support dynamic twt setup Bug: 253348062 Test: SST-stability/WiFi regression/WiFi performance Test pass Signed-off-by: Star Chang <starchang@google.com> Change-Id: I9e6df816c55850e03d7167122f658dad8b77c326
-rw-r--r--BUILD.bazel20
-rw-r--r--Kconfig16
-rw-r--r--Makefile32
-rw-r--r--core.h35
-rw-r--r--debug.h37
-rw-r--r--debugfs.c194
-rw-r--r--debugfs.h58
-rw-r--r--dynamic_twt_manager.c398
-rw-r--r--dynamic_twt_manager.h83
-rw-r--r--main.c131
-rw-r--r--notifier.c105
-rw-r--r--notifier.h33
-rw-r--r--scenes_fsm.c258
-rw-r--r--scenes_fsm.h68
-rw-r--r--tp_monitor.c219
-rw-r--r--tp_monitor.h44
-rw-r--r--wlan_ptracker_client.h37
17 files changed, 1768 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..ae5b988
--- /dev/null
+++ b/core.h
@@ -0,0 +1,35 @@
+// 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 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..3cf4dc4
--- /dev/null
+++ b/debugfs.c
@@ -0,0 +1,194 @@
+// 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 0;
+}
+
+static const struct file_operations dscp_ops = {
+ .open = simple_open,
+ .read = action_read,
+ .write = action_write,
+ .llseek = generic_file_llseek,
+};
+
+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("wlan_ptracker", 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);
+ return 0;
+}
+
+void wlan_ptracker_debugfs_exit(struct wlan_ptracker_debugfs *debugfs)
+{
+ debugfs_remove_recursive(debugfs->root);
+ debugfs->root = NULL;
+}
+
+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 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 + hm->entry_count + 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(cur, next->valid ? next : NULL, 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..9e9eabf
--- /dev/null
+++ b/debugfs.h
@@ -0,0 +1,58 @@
+/* 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>
+
+struct wlan_ptracker_debugfs {
+ struct dentry *root;
+ u32 dscp;
+ u32 ac;
+ u32 action;
+ u32 log_level;
+};
+
+enum {
+ FEATURE_FLAG_TWT,
+ FEATURE_FLAG_MAX
+};
+
+enum {
+ ACTION_DSCP_UPDATE,
+ ACTION_MAX,
+};
+
+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)(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 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..7f22219
--- /dev/null
+++ b/dynamic_twt_manager.c
@@ -0,0 +1,398 @@
+// 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"
+
+static struct dytwt_manager dytwt_mgmt;
+
+#define dytwt_get_manager() (&dytwt_mgmt)
+
+static const char *const reason2str[WLAN_PTRACKER_NOTIFY_MAX] = {
+ "tp", "scene_change", "scene_prep", "suspend", "sta_change",
+};
+
+#define DYMAIC_TWT_CONFIG_ID 3
+#define TWT_WAKE_DURATION 8192
+#define TWT_IDLE_INTERVAL 512000
+#define TWT_WEB_INTERVAL 106496
+#define TWT_YOUTUBE_INTERVAL 10240
+
+static struct dytwt_scene_action dytwt_actions[WLAN_SCENE_MAX] = {
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_WAKE_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_WAKE_DURATION,
+ .wake_interval = TWT_WEB_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_WAKE_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,
+ },
+ },
+};
+
+static int dytwt_client_twt_setup(struct wlan_ptracker_client *client, u32 state)
+{
+ if (!client->dytwt_ops)
+ 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)
+ 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;
+ int ret;
+
+ if (!client->dytwt_ops)
+ return false;
+
+ if (!client->dytwt_ops->get_cap)
+ return false;
+
+ ret = client->dytwt_ops->get_cap(client->priv, &param);
+
+ if (ret)
+ return false;
+
+ ptracker_dbg(core, "device: %d, peer: %d\n", param.device_cap, param.peer_cap);
+ return param.peer_cap && param.device_cap;
+}
+
+static int dytwt_client_twt_pwrstates(struct wlan_ptracker_client *client,
+ struct dytwt_pwr_state *state)
+{
+ if (!client->dytwt_ops)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_pwrstates)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_pwrstates(client->priv, state);
+}
+
+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(void *cur, void *next, char *buf, int len)
+{
+ struct dytwt_entry *c = cur;
+ struct dytwt_entry *n = next;
+ int period_percent = 0, total_percent;
+ u64 period_time = 0, total_time;
+
+ /* get total */
+ dytwt_record_get_pwr(c->pwr.asleep, c->pwr.awake, &total_time, &total_percent);
+
+ /* get period */
+ if (n) {
+ u64 awake = n->pwr.awake > c->pwr.awake ?
+ (n->pwr.awake - c->pwr.awake) : c->pwr.awake;
+ u64 asleep = n->pwr.asleep > c->pwr.asleep ?
+ (n->pwr.asleep - c->pwr.asleep) : c->pwr.asleep;
+ 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\n",
+ c->apply ? "TRUE" : "FALSE", period_time, total_time, period_percent, total_percent,
+ reason2str[c->reason], c->rate);
+}
+
+static void dytwt_mgmt_history_store(struct wlan_ptracker_client *client,
+ struct dytwt_manager *dytwt, struct wlan_scene_event *msg, bool apply)
+{
+ 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 = msg->reason;
+ entry->rate = msg->rate;
+ dytwt_client_twt_pwrstates(client, &entry->pwr);
+ /* prev will be used for decided teardown or not. */
+ dytwt->prev = msg->dst;
+}
+
+#define TWT_HISTORY_BUF_SIZE 10240
+static ssize_t twt_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos)
+{
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+ char *buf;
+ int len;
+ ssize_t ret;
+
+ buf = vmalloc(TWT_HISTORY_BUF_SIZE);
+
+ if (!buf)
+ return 0;
+
+ len = wlan_ptracker_history_read(dytwt->hm, buf, TWT_HISTORY_BUF_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static void update_twt_flag(struct wlan_ptracker_core *core)
+{
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+
+ if (dytwt->feature_flag & BIT(FEATURE_FLAG_TWT))
+ dytwt->feature_flag &= ~BIT(FEATURE_FLAG_TWT);
+ else
+ dytwt->feature_flag |= BIT(FEATURE_FLAG_TWT);
+}
+
+static int dytwt_debugfs_action(struct wlan_ptracker_core *core, u32 action)
+{
+ struct dytwt_pwr_state pwr_state;
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+ struct wlan_ptracker_client *client = core->client;
+
+ switch (action) {
+ case TWT_TEST_SETUP:
+ dytwt_client_twt_setup(client, dytwt->state);
+ break;
+ case TWT_TEST_TEARDOWN:
+ dytwt_client_twt_teardown(client, dytwt->state);
+ 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);
+ break;
+ default:
+ ptracker_err(core, "action %d is not supported!\n", action);
+ return -ENOTSUPP;
+ }
+ return 0;
+}
+
+static ssize_t twt_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;
+
+ return dytwt_debugfs_action(core, action);
+}
+
+static const struct file_operations twt_ops = {
+ .open = simple_open,
+ .read = twt_read,
+ .write = twt_write,
+ .llseek = generic_file_llseek,
+};
+
+/* This function is running in thread context */
+#define TWT_WAIT_STA_READY_TIME 1000
+static int dytwt_scene_change_handler(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;
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+ 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(core, "twt is not supported on device or peer\n");
+ goto out;
+ }
+
+ act = &dytwt_actions[state];
+ ptracker_dbg(core, "twt setup for state: %d, reason: %s!\n",
+ state, reason2str[msg->reason]);
+
+ /* wait for sta ready after connected. */
+ if (msg->reason == WLAN_PTRACKER_NOTIFY_STA_CHANGE)
+ msleep(TWT_WAIT_STA_READY_TIME);
+
+ /* follow action to setup */
+ if (act->action == TWT_ACTION_SETUP) {
+ ret = dytwt_client_twt_setup(client, state);
+ } else {
+ /* tear down was apply during state of perpare change. */
+ apply = true;
+ }
+ apply = ret ? false : true;
+out:
+ /* store record of hostory even twt is not applid! */
+ dytwt_mgmt_history_store(client, dytwt, msg, apply);
+ return ret;
+}
+
+static void dytwt_scene_change_prepare_handler(struct wlan_ptracker_client *client)
+{
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+
+ /* prepare to change state teardown original setup first */
+ if (dytwt->prev < WLAN_SCENE_LOW_LATENCY)
+ 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;
+
+ if (!client)
+ return NOTIFY_OK;
+
+ 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;
+ default:
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static int dytwt_debugfs_init(struct wlan_ptracker_core *core)
+{
+ struct wlan_ptracker_debugfs *debugfs = &core->debugfs;
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+
+ dytwt->feature_flag |= BIT(FEATURE_FLAG_TWT);
+ dytwt->dir = debugfs_create_dir("twt", debugfs->root);
+ if (!dytwt->dir)
+ return -ENODEV;
+ debugfs_create_file("history", 0600, dytwt->dir, core, &twt_ops);
+ debugfs_create_u32("state", 0600, dytwt->dir, &dytwt->state);
+ return 0;
+}
+
+#define DYTWT_RECORD_MAX 50
+static int dytwt_mgmt_init(void)
+{
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+ struct history_manager *hm;
+
+ if (dytwt->dir)
+ debugfs_remove_recursive(dytwt->dir);
+ memset(dytwt, 0, sizeof(*dytwt));
+ dytwt->state = WLAN_SCENE_IDLE;
+ dytwt->prev = WLAN_SCENE_MAX;
+ hm = wlan_ptracker_history_create(DYTWT_RECORD_MAX, sizeof(struct dytwt_entry));
+ if (!hm)
+ return -ENOMEM;
+ strncpy(hm->name, "Dynamic TWT Setup", sizeof(hm->name));
+ hm->priv_read = dytwt_record_priv_read;
+ dytwt->hm = hm;
+ return 0;
+}
+
+static void dytwt_mgmt_exit(void)
+{
+ struct dytwt_manager *dytwt = dytwt_get_manager();
+
+ if (dytwt->dir)
+ debugfs_remove_recursive(dytwt->dir);
+
+ wlan_ptracker_history_destroy(dytwt->hm);
+ memset(dytwt, 0, sizeof(*dytwt));
+}
+
+static struct notifier_block twt_nb = {
+ .priority = 0,
+ .notifier_call = dytwt_notifier_handler,
+};
+
+int dytwt_init(struct wlan_ptracker_core *core)
+{
+ dytwt_mgmt_init();
+ dytwt_debugfs_init(core);
+ return wlan_ptracker_register_notifier(&core->notifier, &twt_nb);
+}
+
+void dytwt_exit(struct wlan_ptracker_core *core)
+{
+ dytwt_mgmt_exit();
+ return wlan_ptracker_unregister_notifier(&core->notifier, &twt_nb);
+}
+
diff --git a/dynamic_twt_manager.h b/dynamic_twt_manager.h
new file mode 100644
index 0000000..fe8b289
--- /dev/null
+++ b/dynamic_twt_manager.h
@@ -0,0 +1,83 @@
+// 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;
+};
+
+struct dytwt_pwr_state {
+ u64 awake;
+ u64 asleep;
+};
+
+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);
+};
+
+enum {
+ TWT_ACTION_SETUP,
+ TWT_ACTION_TEARDOWN,
+ TWT_ACTION_MAX,
+};
+
+enum {
+ TWT_TEST_SETUP,
+ TWT_TEST_TEARDOWN,
+ TWT_TEST_CAP,
+ TWT_TEST_PWRSTATS,
+ TWT_TEST_ONOFF,
+ 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_manager {
+ u32 prev;
+ u32 feature_flag;
+ u32 state;
+ struct history_manager *hm;
+ struct dentry *dir;
+};
+
+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..85f18e7
--- /dev/null
+++ b/main.c
@@ -0,0 +1,131 @@
+// 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"
+
+
+static struct wlan_ptracker_core ptracker_core;
+
+#define get_core() (&ptracker_core)
+
+#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 int wlan_ptracker_core_init(struct wlan_ptracker_core *core)
+{
+ memset(core, 0, sizeof(*core));
+ 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 0;
+}
+
+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);
+ memset(core, 0, sizeof(struct wlan_ptracker_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)
+{
+ struct wlan_ptracker_core *core = get_core();
+
+ if (!core->client) {
+ core->client = client;
+ 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 = get_core();
+
+ if (core->client == client) {
+ client->cb = NULL;
+ core->client = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(wlan_ptracker_unregister_client);
+
+static int __init wlan_ptracker_init(void)
+{
+ struct wlan_ptracker_core *core = get_core();
+ int ret;
+
+ ret = wlan_ptracker_core_init(core);
+ if (ret)
+ goto err;
+ dev_dbg(&core->device, "module init\n");
+ return 0;
+err:
+ wlan_ptracker_core_exit(core);
+ return ret;
+}
+
+static void __exit wlan_ptracker_exit(void)
+{
+ struct wlan_ptracker_core *core = get_core();
+
+ dev_dbg(&core->device, "module exit\n");
+ wlan_ptracker_core_exit(core);
+}
+
+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..7f9712d
--- /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;
+ core->client->core = core;
+ core->client->priv = dev;
+ return tp_monitor_init(&core->tp);
+}
+
+static void down_event_handler(struct wlan_ptracker_core *core)
+{
+ tp_monitor_exit(&core->tp);
+ core->dev = NULL;
+ core->client->core = NULL;
+ core->client->priv = NULL;
+}
+
+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..991863e
--- /dev/null
+++ b/scenes_fsm.c
@@ -0,0 +1,258 @@
+// 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 fsm_to_core(fsm) \
+ (container_of(fsm, struct wlan_ptracker_core, fsm))
+
+static const 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 = 10000,
+ },
+ {
+ .scene = WLAN_SCENE_YOUTUBE,
+ .ac_mask = WMM_AC_ALL_MASK,
+ /* Total >= 10 Mbps && < 50 Mbps */
+ .min_tp_threshold = 10000,
+ .max_tp_threshold = 50000,
+ },
+ {
+ .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,
+ /* Total >= 50 Mbps */
+ .min_tp_threshold = 50000,
+ .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 (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ 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);
+
+ /*
+ * Request twice of transmit events are happing then trans state,
+ * to make sure the state is stable enough.
+ * first time: confirm is false, send prepare first.
+ * (ex: twt can tear down original setup first)
+ * second time: confirm is true and change the state to dst.
+ */
+ if (fsm->confirm) {
+ wlan_ptracker_call_chain(&core->notifier,
+ WLAN_PTRACKER_NOTIFY_SCENE_CHANGE, core);
+ msg->state = msg->dst;
+ fsm->confirm = false;
+ } else {
+ /* call notifier chain */
+ wlan_ptracker_call_chain(&core->notifier,
+ WLAN_PTRACKER_NOTIFY_SCENE_CHANGE_PREPARE, core);
+ fsm->confirm = true;
+ }
+ }
+ 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);
+ }
+
+ /* check state isn't change and not first time do nothing */
+ if (new_state == msg->state && type != WLAN_PTRACKER_NOTIFY_STA_CHANGE)
+ return;
+ /* new state must higher then current state */
+ if (new_state < msg->state && !except)
+ 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:
+ ptracker_dbg(core, "update time (%d)\n",
+ jiffies_to_msecs(jiffies - notifier->prev_event));
+ notifier->prev_event = jiffies;
+ case WLAN_PTRACKER_NOTIFY_STA_CHANGE:
+ 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,
+};
+
+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;
+ /* for first link up setting */
+ fsm->confirm = true;
+ /* 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 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;
+ }
+ 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);
+
+ wlan_ptracker_unregister_notifier(&core->notifier, &scene_nb);
+ 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);
+ }
+ complete(&fsm->event);
+ fsm->conditions = NULL;
+ fsm->reset_cnt = 0;
+}
+
diff --git a/scenes_fsm.h b/scenes_fsm.h
new file mode 100644
index 0000000..b79a78f
--- /dev/null
+++ b/scenes_fsm.h
@@ -0,0 +1,68 @@
+// 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
+};
+
+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 confirm;
+ struct completion event;
+ struct wlan_scene_event msg;
+ struct task_struct *fsm_thread;
+ const struct wlan_state_condition *conditions;
+};
+
+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..6e04d73
--- /dev/null
+++ b/tp_monitor.c
@@ -0,0 +1,219 @@
+// 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;
+ }
+}
+
+/* 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\n", counter->packet_cnt);
+ seq_printf(s, "packet_bytes : %llu\n", counter->packet_bytes);
+ seq_printf(s, "rate (Kbits) : %llu\n", counter->rate / 1000);
+ seq_printf(s, "pps : %llu\n", 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;
+
+ if (!client)
+ return 0;
+ 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..05319a1
--- /dev/null
+++ b/tp_monitor.h
@@ -0,0 +1,44 @@
+// 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;
+};
+
+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..4933960
--- /dev/null
+++ b/wlan_ptracker_client.h
@@ -0,0 +1,37 @@
+// 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_CHANGE,
+ WLAN_PTRACKER_NOTIFY_MAX,
+};
+
+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*/
+