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