blob: 410e114963e92b6aebcd3e1788303ae055cf302d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Wifi performance tracker
*
* Copyright 2022 Google LLC.
*
* Author: Star Chang <[email protected]>
*/
#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;
fallthrough;
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;
}