// SPDX-License-Identifier: GPL-2.0-only /* * Driver for WiFi Performance Tracker * * Copyright 2022 Google LLC. * * Author: Star Chang */ #include #include #include #include #include #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 0; } static const struct file_operations dscp_ops = { .open = simple_open, .read = action_read, .write = action_write, .llseek = generic_file_llseek, }; 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("wlan_ptracker", 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); return 0; } void wlan_ptracker_debugfs_exit(struct wlan_ptracker_debugfs *debugfs) { debugfs_remove_recursive(debugfs->root); debugfs->root = NULL; } 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 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 + hm->entry_count + 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(cur, next->valid ? next : NULL, buf + len, buf_len - len); len += scnprintf(buf + len, buf_len - len, "\n"); ptr += hm->entry_size; } return len; }