summaryrefslogtreecommitdiff
path: root/scenes_fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'scenes_fsm.c')
-rw-r--r--scenes_fsm.c351
1 files changed, 351 insertions, 0 deletions
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;
+}