diff options
Diffstat (limited to 'scenes_fsm.c')
-rw-r--r-- | scenes_fsm.c | 351 |
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; +} |