summaryrefslogtreecommitdiff
path: root/dynamic_twt_manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'dynamic_twt_manager.c')
-rw-r--r--dynamic_twt_manager.c870
1 files changed, 870 insertions, 0 deletions
diff --git a/dynamic_twt_manager.c b/dynamic_twt_manager.c
new file mode 100644
index 0000000..1701a57
--- /dev/null
+++ b/dynamic_twt_manager.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Wifi performance tracker
+ *
+ * Copyright 2022 Google LLC.
+ *
+ * Author: Star Chang <starchang@google.com>
+ */
+#include <linux/debugfs.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include "core.h"
+
+#define DYMAIC_TWT_CONFIG_ID 3
+
+/* for tcp one pair case */
+#define TWT_IDLE_INTERVAL (500 * 1024) /* 512000 */
+#define TWT_IDLE_DURATION (768 * 32) /* 24576 */
+#define TWT_WEB_INTERVAL (104 * 1024) /* 106496 */
+#define TWT_WEB_DURATION (256 * 32) /* 8192 */
+#define TWT_YOUTUBE_INTERVAL (10 * 1024) /* 10240 */
+#define TWT_YOUTUBE_DURATION (256 * 32) /* 8192 */
+
+/* define reason*/
+enum {
+ TWT_SETUP_REASON_FRAMEWORK = WLAN_PTRACKER_NOTIFY_MAX,
+ TWT_SETUP_REASON_FORCE,
+ TWT_SETUP_REASON_RUNTIME,
+ TWT_SETUP_REASON_MAX,
+};
+
+static const char *const reason2str[TWT_SETUP_REASON_MAX] = {
+ "tp", "scene_change", "scene_prep", "suspend", "sta_connect",
+ "sta_discont", "dytwt_enable", "dytwt_disable", "framework",
+ "force", "runtime",
+};
+
+static const char *const state2str[WLAN_SCENE_MAX] = {
+ "Idle", "Web", "Youtube", "Low latency", "Throughput"
+};
+
+static struct dytwt_scene_action dytwt_actions[WLAN_SCENE_MAX + 1] = {
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_IDLE_DURATION,
+ .wake_interval = TWT_IDLE_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_WEB_DURATION,
+ .wake_interval = TWT_WEB_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_TEARDOWN,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_YOUTUBE_DURATION,
+ .wake_interval = TWT_YOUTUBE_INTERVAL,
+ },
+ },
+ {
+ .action = TWT_ACTION_TEARDOWN,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ },
+ },
+ {
+ .action = TWT_ACTION_TEARDOWN,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ },
+ },
+ /* used for force mode */
+ {
+ .action = TWT_ACTION_SETUP,
+ .param = {
+ .config_id = DYMAIC_TWT_CONFIG_ID,
+ .nego_type = 0,
+ .trigger_type = 0,
+ .wake_duration = TWT_IDLE_DURATION,
+ .wake_interval = TWT_IDLE_INTERVAL,
+ },
+ }
+};
+#define TWT_ACTION_SIZE ARRAY_SIZE(dytwt_actions)
+
+static int dytwt_client_twt_setup(struct wlan_ptracker_client *client, u32 state)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->setup)
+ return -EINVAL;
+
+ if (state >= WLAN_SCENE_MAX)
+ return -EINVAL;
+
+ return client->dytwt_ops->setup(client->priv, &dytwt_actions[state].param);
+}
+
+static int dytwt_client_twt_teardown(struct wlan_ptracker_client *client, u32 state)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->teardown)
+ return -EINVAL;
+
+ if (state >= WLAN_SCENE_MAX)
+ return -EINVAL;
+ return client->dytwt_ops->teardown(client->priv, &dytwt_actions[state].param);
+}
+
+static bool dytwt_client_twt_cap(struct wlan_ptracker_client *client)
+{
+ struct dytwt_cap param;
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+ int ret;
+
+ if (!client->dytwt_ops || !client->priv)
+ return false;
+
+ if (!client->dytwt_ops->get_cap)
+ return false;
+
+ ret = client->dytwt_ops->get_cap(client->priv, &param);
+
+ ptracker_dbg(core, "%d, %d, %d, %d\n", param.device_cap, param.peer_cap,
+ param.link_speed, param.rssi);
+ if (ret)
+ return false;
+
+ if (!param.peer_cap || !param.device_cap) {
+ ptracker_err(core, "dytwt is not enabled due to capability: %d, %d\n",
+ param.device_cap, param.peer_cap);
+ return false;
+ }
+
+ if (param.rssi != 0 && param.rssi < dytwt->rssi_threshold) {
+ ptracker_err(dytwt->core, "dytwt is not enabled due to rssi %d < %d\n",
+ param.rssi, dytwt->rssi_threshold);
+ return false;
+ }
+
+ if (param.link_speed < dytwt->link_threshold) {
+ ptracker_err(dytwt->core, "dytwt is not enabled due to linkspeed %d < %d\n",
+ param.link_speed, dytwt->link_threshold);
+ return false;
+ }
+ return true;
+}
+
+static int dytwt_client_twt_pwrstates(struct wlan_ptracker_client *client,
+ struct dytwt_pwr_state *state)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_pwrstates)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_pwrstates(client->priv, state);
+}
+
+static int dytwt_client_twt_get_stats(struct wlan_ptracker_client *client,
+ struct dytwt_stats *stats)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_stats)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_stats(client->priv, stats);
+}
+
+static int dytwt_client_twt_get_status(struct wlan_ptracker_client *client,
+ struct dytwt_status *status)
+{
+ if (!client->dytwt_ops || !client->priv)
+ return -EINVAL;
+
+ if (!client->dytwt_ops->get_status)
+ return -EINVAL;
+
+ return client->dytwt_ops->get_status(client->priv, status);
+}
+
+static inline void dytwt_record_get_pwr(u64 asleep, u64 awake, u64 *total, int *percent)
+{
+ /* for percent */
+ *total = (asleep + awake) / 100;
+ *percent = (*total == 0) ? 0 : (asleep / *total);
+ /* trans 100 us to ms */
+ *total /= 10;
+}
+
+static int dytwt_record_priv_read(struct wlan_ptracker_core *core, void *cur, void *next,
+ char *buf, int len)
+{
+ struct dytwt_entry *c = cur;
+ struct dytwt_entry *n = next;
+ int period_percent, total_percent;
+ u64 period_time, total_time;
+ u64 awake, asleep;
+
+ /* next is the current state */
+ if (n->pwr.asleep < c->pwr.asleep) {
+ struct dytwt_pwr_state pwr;
+ dytwt_client_twt_pwrstates(core->client, &pwr);
+ awake = pwr.awake - c->pwr.awake;
+ asleep = pwr.asleep - c->pwr.asleep;
+ /* get total */
+ dytwt_record_get_pwr(pwr.asleep, pwr.awake, &total_time, &total_percent);
+ } else {
+ /* get period */
+ awake = n->pwr.awake - c->pwr.awake;
+ asleep = n->pwr.asleep - c->pwr.asleep;
+ /* get total */
+ dytwt_record_get_pwr(c->pwr.asleep, c->pwr.awake, &total_time, &total_percent);
+ }
+ dytwt_record_get_pwr(asleep, awake, &period_time, &period_percent);
+ return scnprintf(buf, len,
+ "Applied: %s, Time: %llu (%llu) ms, Percent: %d%% (%d%%) Reason: %s, Rate: %d",
+ c->apply ? "TRUE" : "FALSE", period_time, total_time, period_percent, total_percent,
+ reason2str[c->reason], c->rate);
+}
+
+static void dytwt_counter_update(struct dytwt_manager *dytwt, struct dytwt_pwr_state *pwr)
+{
+ struct dytwt_counters *counter = &dytwt->counters;
+ struct dytwt_statistic *stat = &counter->scene[dytwt->prev];
+ u64 asleep = pwr->asleep - counter->prev_asleep;
+ u64 awake = pwr->awake - counter->prev_awake;
+ u64 count = pwr->count - counter->prev_asleep_cnt;
+
+ stat->asleep += asleep;
+ stat->awake += awake;
+ counter->prev_asleep = pwr->asleep;
+ counter->prev_awake = pwr->awake;
+ counter->prev_asleep_cnt = pwr->count;
+ counter->total_awake += awake;
+ counter->total_sleep += asleep;
+ counter->total_sleep_cnt += count;
+}
+
+static void dytwt_mgmt_history_store(struct wlan_ptracker_client *client,
+ struct dytwt_manager *dytwt, struct wlan_scene_event *msg, bool apply, u32 reason)
+{
+ struct dytwt_entry *entry;
+
+ /* record assign base*/
+ entry = wlan_ptracker_history_store(dytwt->hm, msg->dst);
+ if (!entry)
+ return;
+ /* record private values */
+ entry->apply = apply;
+ entry->reason = reason;
+ entry->rate = msg->rate;
+ dytwt_client_twt_pwrstates(client, &entry->pwr);
+ dytwt_counter_update(dytwt, &entry->pwr);
+ /* prev will be used for decided teardown or not. */
+ dytwt->prev = msg->dst;
+}
+
+/* This function is running in thread context */
+static int _dytwt_scene_change_handler(struct dytwt_manager *dytwt,
+ struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct wlan_scene_event *msg = &core->fsm.msg;
+ struct dytwt_scene_action *act;
+ bool apply = false;
+ u32 state = msg->dst;
+ int ret = 0;
+
+ if (!(dytwt->feature_flag & BIT(FEATURE_FLAG_TWT)))
+ goto out;
+
+ if (!dytwt_client_twt_cap(client)) {
+ ptracker_dbg(dytwt->core, "twt is not supported on device or peer\n");
+ goto out;
+ }
+ act = &dytwt_actions[state];
+
+ /* follow action to setup */
+ if (act->action == TWT_ACTION_SETUP)
+ ret = dytwt_client_twt_setup(client, state);
+ apply = ret ? false : true;
+out:
+ /* store record of history even twt is not applied */
+ dytwt_mgmt_history_store(client, dytwt, msg, apply, msg->reason);
+ ptracker_dbg(dytwt->core, "twt setup for state: %d, reason: %s, ret: %d\n",
+ state, reason2str[msg->reason], ret);
+ return ret;
+}
+
+static void dytwt_delay_setup(struct work_struct *work)
+{
+ struct dytwt_manager *dytwt = container_of(work, struct dytwt_manager, setup_wq.work);
+ struct wlan_ptracker_core *core = dytwt->core;
+ struct wlan_ptracker_client *client;
+
+ if (!core)
+ return;
+
+ client = core->client;
+ /* for first time update value is required*/
+ dytwt->twt_cap = dytwt_client_twt_cap(client);
+ _dytwt_scene_change_handler(dytwt, client);
+}
+
+#define TWT_WAIT_STA_READY_TIME 2000
+static int dytwt_scene_change_handler(struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct wlan_scene_event *msg = &core->fsm.msg;
+
+ if (msg->reason == WLAN_PTRACKER_NOTIFY_STA_CONNECT)
+ schedule_delayed_work(&dytwt->setup_wq, msecs_to_jiffies(TWT_WAIT_STA_READY_TIME));
+ else
+ _dytwt_scene_change_handler(dytwt, client);
+ return 0;
+}
+
+#define TWT_HISTORY_BUF_SIZE 10240
+static ssize_t twt_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ struct dytwt_manager *dytwt = core->dytwt;
+ char *buf;
+ int len;
+ ssize_t ret;
+
+ buf = vmalloc(TWT_HISTORY_BUF_SIZE);
+
+ if (!buf)
+ return -ENOMEM;
+
+ len = wlan_ptracker_history_read(core, dytwt->hm, buf, TWT_HISTORY_BUF_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations twt_ops = {
+ .open = simple_open,
+ .read = twt_read,
+ .llseek = generic_file_llseek,
+};
+
+static void dytwt_force_twt_setup(struct wlan_ptracker_client *client, struct dytwt_manager *dytwt,
+ u32 reason)
+{
+ int ret = 0;
+ bool apply = false;
+ u32 state = dytwt->state;
+ struct wlan_scene_event msg;
+ struct dytwt_scene_action *act = &dytwt_actions[state];
+
+ switch(act->action) {
+ case TWT_ACTION_SETUP:
+ ret = dytwt_client_twt_setup(client, state);
+ break;
+ case TWT_ACTION_TEARDOWN:
+ ret = dytwt_client_twt_teardown(client, state);
+ break;
+ default:
+ break;
+ }
+ apply = ret ? false : true;
+ msg.dst = dytwt->state;
+ /* store record of history even twt is not applied */
+ dytwt_mgmt_history_store(client, dytwt, &msg, apply, reason);
+}
+
+static inline void twt_enable(struct wlan_ptracker_client *client, bool enable, u32 reason)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+
+ if (enable) {
+ dytwt->feature_flag |= BIT(FEATURE_FLAG_TWT);
+ dytwt_scene_change_handler(client);
+ } else {
+ dytwt->state = WLAN_SCENE_TPUT;
+ dytwt_force_twt_setup(client, dytwt, reason);
+ dytwt->feature_flag &= ~BIT(FEATURE_FLAG_TWT);
+ }
+}
+
+#define DYTWT_RUNTIME_TIMER 2000
+static void dytwt_runtime(struct work_struct *work)
+{
+ struct dytwt_manager *dytwt = container_of(work, struct dytwt_manager, wq.work);
+ struct dytwt_scene_action *act;
+ struct wlan_ptracker_client *client;
+
+ if (!dytwt->core)
+ goto end;
+
+ if (dytwt->prev == WLAN_SCENE_MAX)
+ goto end;
+
+ client = dytwt->core->client;
+ act = &dytwt_actions[dytwt->prev];
+ /* update twt_cap periodically */
+ dytwt->twt_cap = dytwt_client_twt_cap(client);
+ if (act->action == TWT_ACTION_SETUP && !dytwt->twt_cap) {
+ dytwt->state = WLAN_SCENE_TPUT;
+ ptracker_dbg(dytwt->core, "teardown twt due to hit threshold\n");
+ dytwt_force_twt_setup(client, dytwt, TWT_SETUP_REASON_RUNTIME);
+ }
+end:
+ schedule_delayed_work(&dytwt->wq, msecs_to_jiffies(DYTWT_RUNTIME_TIMER));
+}
+
+static void update_twt_flag(struct wlan_ptracker_core *core, struct dytwt_manager *dytwt)
+{
+ twt_enable(core->client, !(dytwt->feature_flag & BIT(FEATURE_FLAG_TWT)),
+ TWT_SETUP_REASON_FORCE);
+}
+
+static void update_twt_parameters(struct dytwt_manager *dytwt)
+{
+ u32 state = dytwt->state;
+ struct dytwt_scene_action *cfg_act = &dytwt_actions[WLAN_SCENE_MAX];
+ struct dytwt_scene_action *cur_act = &dytwt_actions[state];
+
+ cur_act->param.wake_duration = cfg_act->param.wake_duration;
+ cur_act->param.wake_interval = cfg_act->param.wake_interval;
+ cur_act->action = cfg_act->action;
+}
+
+static void dytwt_stats_dump(struct wlan_ptracker_client *client, struct dytwt_manager *dytwt)
+{
+ struct dytwt_stats stats;
+ struct wlan_ptracker_core *core = dytwt->core;
+
+ stats.config_id = DYMAIC_TWT_CONFIG_ID;
+ dytwt_client_twt_get_stats(client, &stats);
+
+ ptracker_info(core, "rx_ucast_pkts: %d, rx_pkts_retried: %d\n",
+ stats.rx_ucast_pkts, stats.rx_pkts_retried);
+ ptracker_info(core, "rx_pkt_sz_avg: %d, rx_pkts_avg: %d\n",
+ stats.rx_pkt_sz_avg, stats.rx_pkts_avg);
+ ptracker_info(core, "rx_pkts_min: %d, rx_pkts_max: %d\n",
+ stats.rx_pkts_min, stats.rx_pkts_max);
+ ptracker_info(core, "tx_ucast_pkts: %d, tx_failures: %d\n",
+ stats.tx_ucast_pkts, stats.tx_failures);
+ ptracker_info(core, "tx_pkt_sz_avg: %d, tx_pkts_avg: %d\n",
+ stats.tx_pkt_sz_avg, stats.tx_pkts_avg);
+ ptracker_info(core, "tx_pkts_min: %d, tx_pkts_max: %d\n",
+ stats.tx_pkts_min, stats.tx_pkts_max);
+ ptracker_info(core, "sp_seq: %d, eosp_count: %d, eosp_dur_avg: %d\n",
+ stats.sp_seq, stats.eosp_count, stats.eosp_dur_avg);
+}
+
+static void dytwt_status_dump(struct wlan_ptracker_client *client, struct dytwt_manager *dytwt)
+{
+ struct dytwt_status status;
+
+ status.config_id = DYMAIC_TWT_CONFIG_ID;
+ dytwt_client_twt_get_status(client, &status);
+
+ ptracker_info(dytwt->core, "config_id: %d, flow_id: %d, flow_flags: %x\n",
+ status.config_id, status.flow_id, status.flow_flags);
+ ptracker_info(dytwt->core, "setup_cmd: %d, channel: %d, nego_type: %d\n",
+ status.setup_cmd, status.channel, status.nego_type);
+ ptracker_info(dytwt->core, "wake_dur: %d, wake_int: %d\n",
+ status.wake_dur, status.wake_int);
+}
+
+static int dytwt_debugfs_action(struct wlan_ptracker_core *core, u32 action)
+{
+ struct dytwt_pwr_state pwr_state;
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct wlan_ptracker_client *client = core->client;
+
+ switch (action) {
+ case TWT_TEST_FORCE_STATE:
+ dytwt_force_twt_setup(client, dytwt, TWT_SETUP_REASON_FORCE);
+ break;
+ case TWT_TEST_CAP:
+ dytwt_client_twt_cap(client);
+ break;
+ case TWT_TEST_PWRSTATS:
+ dytwt_client_twt_pwrstates(client, &pwr_state);
+ break;
+ case TWT_TEST_ONOFF:
+ update_twt_flag(core, dytwt);
+ break;
+ case TWT_TEST_SET_PARAM:
+ update_twt_parameters(dytwt);
+ break;
+ case TWT_TEST_DUMP_STATS:
+ dytwt_stats_dump(client, dytwt);
+ break;
+ case TWT_TEST_DUMP_STATUS:
+ dytwt_status_dump(client, dytwt);
+ break;
+ default:
+ ptracker_err(core, "action %d is not supported\n", action);
+ return -ENOTSUPP;
+ }
+ return 0;
+}
+
+static ssize_t twt_params_write(struct file *file, const char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ u32 action;
+
+ if (kstrtouint_from_user(buf, len, 10, &action))
+ return -EFAULT;
+
+ dytwt_debugfs_action(core, action);
+ return len;
+}
+
+static int dytwt_params_read(char *buf, int len)
+{
+ struct dytwt_scene_action *act;
+ struct dytwt_setup_param *param;
+ int count = 0;
+ int i;
+
+ count += scnprintf(buf + count, len - count,
+ "===================\n");
+ for (i = 0 ; i < TWT_ACTION_SIZE; i++) {
+ act = &dytwt_actions[i];
+ param = &act->param;
+ count += scnprintf(buf + count, len - count,
+ "state: %d, action: %d\n", i, act->action);
+ count += scnprintf(buf + count, len - count,
+ "config_id: %d, nego_type: %d\n",
+ param->config_id, param->nego_type);
+ count += scnprintf(buf + count, len - count,
+ "wake_interval: %u\n", param->wake_interval);
+ count += scnprintf(buf + count, len - count,
+ "wake_duration: %u\n", param->wake_duration);
+ count += scnprintf(buf + count, len - count,
+ "===================\n");
+ }
+ return count;
+}
+
+#define TWT_PARAM_BUF_SIZE 1024
+static ssize_t twt_params_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos)
+{
+ char *buf;
+ int len;
+ int ret;
+
+ buf = vmalloc(TWT_PARAM_BUF_SIZE);
+ if (!buf)
+ return -ENOMEM;
+ len = dytwt_params_read(buf, TWT_PARAM_BUF_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations twt_params_ops = {
+ .open = simple_open,
+ .read = twt_params_read,
+ .write = twt_params_write,
+ .llseek = generic_file_llseek,
+};
+
+static int dytwt_statistic_read(struct wlan_ptracker_core *core, char *buf, int len)
+{
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct dytwt_counters *counter = &dytwt->counters;
+ struct dytwt_statistic *ds;
+ struct dytwt_pwr_state pwr;
+ int buf_count = 0;
+ int i, percent;
+ u64 total, awake, asleep, count;
+
+ buf_count += scnprintf(buf + buf_count, len - buf_count,
+ "==== Dynamic TWT Setup Statistics ===\n");
+ dytwt_client_twt_pwrstates(dytwt->core->client, &pwr);
+ for (i = 0 ; i < WLAN_SCENE_MAX; i++) {
+ ds = &counter->scene[i];
+ awake = ds->awake;
+ asleep = ds->asleep;
+ if (i == dytwt->prev) {
+ awake += pwr.awake - counter->prev_awake;
+ asleep += pwr.asleep - counter->prev_asleep;
+ }
+ dytwt_record_get_pwr(asleep, awake, &total, &percent);
+ buf_count += scnprintf(buf + buf_count, len - buf_count,
+ "%s, total: %llu, awake: %llu, asleep: %llu (%d%%)\n", state2str[i], total,
+ awake / 1000, asleep / 1000, percent);
+ }
+
+ awake = counter->total_awake + pwr.awake - counter->prev_awake;
+ asleep = counter->total_sleep + pwr.asleep - counter->prev_asleep;
+ count = counter->total_sleep_cnt + pwr.count - counter->prev_asleep_cnt;
+ dytwt_record_get_pwr(asleep, awake, &total, &percent);
+ buf_count += scnprintf(buf + buf_count, len - buf_count,
+ "All, total: %llu, awake: %llu, asleep: %llu (%d%%), sleep cnt: %llu\n",
+ total, awake / 1000, asleep / 1000, percent, count);
+ return buf_count;
+}
+
+#define TWT_STATISTIC_SIZE 512
+static ssize_t twt_statistic_read(struct file *file, char __user *userbuf, size_t count,
+ loff_t *ppos)
+{
+ struct wlan_ptracker_core *core = file->private_data;
+ char *buf;
+ int len;
+ int ret;
+
+ buf = vmalloc(TWT_STATISTIC_SIZE);
+ if (!buf)
+ return -ENOMEM;
+
+ len = dytwt_statistic_read(core, buf, TWT_STATISTIC_SIZE);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+ vfree(buf);
+ return ret;
+}
+
+static const struct file_operations twt_statistic_ops = {
+ .open = simple_open,
+ .read = twt_statistic_read,
+ .llseek = generic_file_llseek,
+};
+
+static void dytwt_scene_change_prepare_handler(struct wlan_ptracker_client *client)
+{
+ struct wlan_ptracker_core *core = client->core;
+ struct dytwt_manager *dytwt = core->dytwt;
+ u32 prev_state = dytwt->prev;
+
+ if (!(dytwt->feature_flag & BIT(FEATURE_FLAG_TWT)))
+ return;
+
+ /*
+ * prepare to change state, teardown the original setup first.
+ * This change is not recorded in history.
+ */
+ if (dytwt_actions[prev_state].action == TWT_ACTION_SETUP)
+ dytwt_client_twt_teardown(client, dytwt->prev);
+}
+
+static int dytwt_notifier_handler(struct notifier_block *nb, unsigned long event, void *ptr)
+{
+ struct wlan_ptracker_core *core = ptr;
+ struct wlan_ptracker_client *client = core->client;
+ struct dytwt_manager *dytwt = core->dytwt;
+
+ switch (event) {
+ case WLAN_PTRACKER_NOTIFY_SCENE_CHANGE:
+ dytwt_scene_change_handler(client);
+ break;
+ case WLAN_PTRACKER_NOTIFY_SCENE_CHANGE_PREPARE:
+ dytwt_scene_change_prepare_handler(client);
+ break;
+ case WLAN_PTRACKER_NOTIFY_STA_CONNECT:
+ schedule_delayed_work(&dytwt->wq, msecs_to_jiffies(DYTWT_RUNTIME_TIMER));
+ break;
+ case WLAN_PTRACKER_NOTIFY_STA_DISCONNECT:
+ cancel_delayed_work_sync(&dytwt->wq);
+ break;
+ case WLAN_PTRACKER_NOTIFY_DYTWT_ENABLE:
+ twt_enable(client, true, TWT_SETUP_REASON_FRAMEWORK);
+ break;
+ case WLAN_PTRACKER_NOTIFY_DYTWT_DISABLE:
+ twt_enable(client, false, TWT_SETUP_REASON_FRAMEWORK);
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static ssize_t dytwt_dumpstate_statistic(struct dytwt_manager *dytwt, char *buf)
+{
+ return dytwt_statistic_read(dytwt->core, buf, PAGE_SIZE);
+}
+
+static ssize_t dytwt_dumpstate_history(struct dytwt_manager *dytwt, char *buf)
+{
+ return wlan_ptracker_history_read(dytwt->core, dytwt->hm, buf, PAGE_SIZE);
+}
+
+static struct dytwt_kobj_attr attr_twt_history =
+ __ATTR(history, 0664, dytwt_dumpstate_history, NULL);
+
+static struct dytwt_kobj_attr attr_twt_statistic =
+ __ATTR(statistic, 0664, dytwt_dumpstate_statistic, NULL);
+
+static struct attribute *default_file_attrs[] = {
+ &attr_twt_history.attr,
+ &attr_twt_statistic.attr,
+ NULL,
+};
+
+static ssize_t dytwt_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ struct dytwt_manager *dytwt;
+ struct dytwt_kobj_attr *dytwt_attr;
+ int ret = -EIO;
+
+ dytwt = container_of(kobj, struct dytwt_manager, kobj);
+ dytwt_attr = container_of(attr, struct dytwt_kobj_attr, attr);
+
+ if (dytwt_attr->show)
+ ret = dytwt_attr->show(dytwt, buf);
+ return ret;
+}
+
+static ssize_t dytwt_sysfs_store(struct kobject *kobj, struct attribute *attr, const char *buf,
+ size_t count)
+{
+ struct dytwt_manager *dytwt;
+ struct dytwt_kobj_attr *dytwt_attr;
+ int ret = -EIO;
+
+ dytwt = container_of(kobj, struct dytwt_manager, kobj);
+ dytwt_attr = container_of(attr, struct dytwt_kobj_attr, attr);
+
+ if (dytwt_attr->show)
+ ret = dytwt_attr->store(dytwt, buf, count);
+ return ret;
+
+}
+
+static struct sysfs_ops dytwt_sysfs_ops = {
+ .show = dytwt_sysfs_show,
+ .store = dytwt_sysfs_store,
+};
+
+static struct kobj_type dytwt_ktype = {
+ .sysfs_ops = &dytwt_sysfs_ops,
+ .default_attrs = default_file_attrs,
+};
+
+static int dytwt_sysfs_init(struct dytwt_manager *dytwt, struct wlan_ptracker_debugfs *debugfs)
+{
+ int ret;
+
+ ret = kobject_init_and_add(&dytwt->kobj, &dytwt_ktype, &debugfs->kobj, "twt");
+ if (ret)
+ kobject_put(&dytwt->kobj);
+ return ret;
+}
+
+static void dytwt_sysfs_exit(struct dytwt_manager *dytwt)
+{
+ kobject_del(&dytwt->kobj);
+ kobject_put(&dytwt->kobj);
+}
+
+static int dytwt_debugfs_init(struct wlan_ptracker_core *core)
+{
+ struct wlan_ptracker_debugfs *debugfs = &core->debugfs;
+ struct dytwt_manager *dytwt = core->dytwt;
+ struct dytwt_scene_action *act = &dytwt_actions[WLAN_SCENE_MAX];
+
+ dytwt->feature_flag |= BIT(FEATURE_FLAG_TWT);
+ dytwt->dir = debugfs_create_dir("twt", debugfs->root);
+ if (!dytwt->dir)
+ return -ENODEV;
+
+ debugfs_create_file("history", 0666, dytwt->dir, core, &twt_ops);
+ debugfs_create_file("statistics", 0666, dytwt->dir, core, &twt_statistic_ops);
+ debugfs_create_file("twt_params", 0666, dytwt->dir, core, &twt_params_ops);
+ debugfs_create_u32("state", 0666, dytwt->dir, &dytwt->state);
+ debugfs_create_u32("wake_interval", 0666, dytwt->dir, &act->param.wake_interval);
+ debugfs_create_u32("wake_duration", 0666, dytwt->dir, &act->param.wake_duration);
+ debugfs_create_u32("action", 0666, dytwt->dir, &act->action);
+ debugfs_create_u32("feature_flag", 0666, dytwt->dir, &dytwt->feature_flag);
+ dytwt_sysfs_init(dytwt, debugfs);
+ return 0;
+}
+
+static void dytwt_debugfs_exit(struct dytwt_manager *dytwt)
+{
+ if (dytwt->dir)
+ debugfs_remove_recursive(dytwt->dir);
+ dytwt_sysfs_exit(dytwt);
+}
+
+#define TWT_DEFAULT_MIN_LINK_SPEED (180000)
+#define TWT_DEFAULT_MIN_RSSI (-70)
+#define DYTWT_RECORD_MAX 30
+static struct dytwt_manager *dytwt_mgmt_init(struct wlan_ptracker_core *core)
+{
+ struct history_manager *hm;
+ struct dytwt_manager *dytwt = kzalloc(sizeof(struct dytwt_manager), GFP_KERNEL);
+
+ if (!dytwt)
+ return NULL;
+
+ dytwt->state = WLAN_SCENE_IDLE;
+ dytwt->prev = WLAN_SCENE_MAX;
+ dytwt->core = core;
+ dytwt->link_threshold = TWT_DEFAULT_MIN_LINK_SPEED;
+ dytwt->rssi_threshold = TWT_DEFAULT_MIN_RSSI;
+ INIT_DELAYED_WORK(&dytwt->wq, dytwt_runtime);
+ INIT_DELAYED_WORK(&dytwt->setup_wq, dytwt_delay_setup);
+ hm = wlan_ptracker_history_create(DYTWT_RECORD_MAX, sizeof(struct dytwt_entry));
+ if (!hm) {
+ kfree(dytwt);
+ return NULL;
+ }
+ strncpy(hm->name, "Dynamic TWT Setup", sizeof(hm->name));
+ hm->priv_read = dytwt_record_priv_read;
+ dytwt->hm = hm;
+
+ return dytwt;
+}
+
+static void dytwt_mgmt_exit(struct dytwt_manager *dytwt)
+{
+ cancel_delayed_work_sync(&dytwt->wq);
+ cancel_delayed_work_sync(&dytwt->setup_wq);
+ wlan_ptracker_history_destroy(dytwt->hm);
+ kfree(dytwt);
+}
+
+static struct notifier_block twt_nb = {
+ .priority = 0,
+ .notifier_call = dytwt_notifier_handler,
+};
+
+int dytwt_init(struct wlan_ptracker_core *core)
+{
+ core->dytwt = dytwt_mgmt_init(core);
+ dytwt_debugfs_init(core);
+ return wlan_ptracker_register_notifier(&core->notifier, &twt_nb);
+}
+
+void dytwt_exit(struct wlan_ptracker_core *core)
+{
+ struct dytwt_manager *dytwt = core->dytwt;
+
+ core->dytwt = NULL;
+ wlan_ptracker_unregister_notifier(&core->notifier, &twt_nb);
+
+ if (!dytwt)
+ return;
+
+ dytwt_debugfs_exit(dytwt);
+ dytwt_mgmt_exit(dytwt);
+}