diff options
author | Robin Peng <robinpeng@google.com> | 2022-11-14 04:55:41 +0000 |
---|---|---|
committer | Robin Peng <robinpeng@google.com> | 2022-11-14 04:55:41 +0000 |
commit | c242902003d6a5479ddb12b5f8685ceb2ddc7324 (patch) | |
tree | 9539c91fb25c0a764c450175d6d39d4556865047 | |
parent | 6c9312a5b819594e65624f69b61588279a47a082 (diff) | |
parent | 8f70f3b22b29e0de3ab13478ab08c842ee221d7a (diff) | |
download | wlan_ptracker-c242902003d6a5479ddb12b5f8685ceb2ddc7324.tar.gz |
Merge android13-gs-pixel-5.10-tm-qpr3 into android13-gs-pixel-5.10-udcandroid-u-preview-2_r0.4android-u-preview-2_r0.3android-u-preview-2_r0.2android-u-preview-1_r0.4android-u-preview-1_r0.3android-u-preview-1_r0.2android-gs-raviole-5.10-u-preview-2android-gs-raviole-5.10-u-preview-1android-gs-pantah-5.10-u-preview-2android-gs-pantah-5.10-u-preview-1android-gs-bluejay-5.10-u-preview-2android-gs-bluejay-5.10-u-preview-1
Bug: 255246572
Change-Id: I7e12d9b01fe646cca8623f9ff87aa4b8941f5135
Signed-off-by: Robin Peng <robinpeng@google.com>
-rw-r--r-- | BUILD.bazel | 20 | ||||
-rw-r--r-- | Kconfig | 16 | ||||
-rw-r--r-- | Makefile | 32 | ||||
-rw-r--r-- | core.h | 36 | ||||
-rw-r--r-- | debug.h | 37 | ||||
-rw-r--r-- | debugfs.c | 248 | ||||
-rw-r--r-- | debugfs.h | 75 | ||||
-rw-r--r-- | dynamic_twt_manager.c | 870 | ||||
-rw-r--r-- | dynamic_twt_manager.h | 149 | ||||
-rw-r--r-- | main.c | 117 | ||||
-rw-r--r-- | notifier.c | 105 | ||||
-rw-r--r-- | notifier.h | 33 | ||||
-rw-r--r-- | scenes_fsm.c | 351 | ||||
-rw-r--r-- | scenes_fsm.h | 78 | ||||
-rw-r--r-- | tp_monitor.c | 228 | ||||
-rw-r--r-- | tp_monitor.h | 48 | ||||
-rw-r--r-- | wlan_ptracker_client.h | 42 |
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__", + ], +) @@ -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 $(@) @@ -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 */ @@ -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, ¶m); + + 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 */ @@ -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(¬ifier->notifier_head, nb); +} + +void wlan_ptracker_unregister_notifier(struct wlan_ptracker_notifier *notifier, + struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(¬ifier->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(¬ifier->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(¬ifier->nb); + /* init notifier chain to notify plugin modules */ + BLOCKING_INIT_NOTIFIER_HEAD(¬ifier->notifier_head); +} + +void wlan_ptracker_notifier_exit(struct wlan_ptracker_notifier *notifier) +{ + /* reset notifier */ + BLOCKING_INIT_NOTIFIER_HEAD(¬ifier->notifier_head); + /* unregister netdevice notifier*/ + unregister_netdevice_notifier(¬ifier->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*/ |