// SPDX-License-Identifier: GPL-2.0-only /* * Driver for Wifi performance tracker * * Copyright 2022 Google LLC. * * Author: Star Chang */ #include #include #include #include #include "core.h" #define tp_to_core(_tp) container_of(_tp, struct wlan_ptracker_core, tp) static void tp_rate_pps_update(struct tp_monitor_counts *counts) { unsigned long cur_cnt, cur_bytes; struct tp_monitor_counts *count; int i; for (i = 0 ; i < TPM_SIZE_MAX; i++) { count = &counts[i]; cur_bytes = count->packet_bytes; cur_cnt = count->packet_cnt; count->rate = (cur_bytes - count->pre_packet_bytes) << 3; count->pps = cur_cnt - count->pre_packet_cnt; count->pre_packet_cnt = cur_cnt; count->pre_packet_bytes = cur_bytes; #ifdef TP_DEBUG count->max_packet_bytes = max(count->max_packet_bytes, count->packet_bytes); count->max_packet_cnt = max(count->max_packet_cnt, count->packet_cnt); count->max_pps = max(count->max_pps, count->pps); count->max_rate = max(count->max_rate, count->rate); #endif /* TP_DEBUG */ } } /* TODO: fine-tune period */ #define TPM_TIMER_PERIOD 1000 static void tp_timer_callback(struct timer_list *t) { struct tp_monitor_stats *stats = from_timer(stats, t, tp_timer); struct wlan_ptracker_core *core = tp_to_core(stats); /* update tx */ tp_rate_pps_update(stats->tx); /* update rx */ tp_rate_pps_update(stats->rx); mod_timer(t, jiffies + msecs_to_jiffies(TPM_TIMER_PERIOD)); /* adjust scenes */ wlan_ptracker_call_chain(&core->notifier, WLAN_PTRACKER_NOTIFY_TP, core); } static inline void tp_timer_start(struct tp_monitor_stats *stats) { /* update rate per second */ timer_setup(&stats->tp_timer, tp_timer_callback, 0); mod_timer(&stats->tp_timer, jiffies + msecs_to_jiffies(TPM_TIMER_PERIOD)); } static inline void tp_timer_stop(struct tp_monitor_stats *stats) { del_timer_sync(&stats->tp_timer); } static void tp_update_counter(struct wlan_ptracker_core *core, struct tp_monitor_counts *counts, u8 dscp, struct sk_buff *skb) { u8 wmm_ac = core->dscp_to_ac[dscp]; /* update total counters */ counts[WMM_AC_MAX].packet_cnt++; counts[WMM_AC_MAX].packet_bytes += skb->len; /* update ac counters */ counts[wmm_ac].packet_cnt++; counts[wmm_ac].packet_bytes += skb->len; } static u32 tp_monitor_nf_input(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct wlan_ptracker_core *core = priv; struct net_device *dev = skb->dev; u8 dscp; if (dev != core->dev) goto out; dscp = ip_hdr(skb)->version == 4 ? ipv4_get_dsfield(ip_hdr(skb)) >> DSCP_SHIFT : ipv6_get_dsfield(ipv6_hdr(skb)) >> DSCP_SHIFT; tp_info(&core->tp, "rx packets %s, dscp: %d, ip.ver: %d, len: %d, %d\n", dev->name, dscp, ip_hdr(skb)->version, skb->len, skb->data_len); tp_update_counter(core, core->tp.rx, dscp, skb); out: return NF_ACCEPT; } static u32 tp_monitor_nf_output(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct wlan_ptracker_core *core = priv; struct net_device *dev = skb->dev; u8 dscp; if (dev != core->dev) goto out; dscp = ip_hdr(skb)->version == 4 ? ipv4_get_dsfield(ip_hdr(skb)) >> DSCP_SHIFT : ipv6_get_dsfield(ipv6_hdr(skb)) >> DSCP_SHIFT; tp_info(&core->tp, "tx packets %s, dscp:%d, ip.ver: %d, len: %d\n", dev->name, dscp, ip_hdr(skb)->version, skb->data_len); tp_update_counter(core, core->tp.tx, dscp, skb); out: return NF_ACCEPT; } static struct nf_hook_ops wlan_ptracker_nfops[] = { { .hook = tp_monitor_nf_input, .pf = NFPROTO_INET, .hooknum = NF_INET_PRE_ROUTING, .priority = INT_MAX, }, { .hook = tp_monitor_nf_output, .pf = NFPROTO_INET, .hooknum = NF_INET_POST_ROUTING, .priority = INT_MAX, }, }; #define WLAN_PTRACKER_NF_LEN ARRAY_SIZE(wlan_ptracker_nfops) static int tp_show(struct seq_file *s, void *unused) { struct tp_monitor_counts *counter, *counters = s->private; int i; for (i = 0 ; i < TPM_SIZE_MAX; i++) { counter = &counters[i]; if (i < WMM_AC_MAX) seq_printf(s, "AC %d ->\n", i); else seq_puts(s, "Total ->\n"); seq_printf(s, "packet_cnt : %llu (%llu)\n", counter->packet_cnt, counter->max_packet_cnt); seq_printf(s, "packet_bytes : %llu (%llu)\n", counter->packet_bytes, counter->max_packet_bytes); seq_printf(s, "rate (Kbits) : %llu (%llu)\n", counter->rate / 1000, counter->max_rate / 1000); seq_printf(s, "pps : %llu (%llu)\n", counter->pps, counter->pps); } return 0; } static int counters_open(struct inode *inode, struct file *file) { return single_open(file, tp_show, inode->i_private); } static const struct file_operations counter_ops = { .open = counters_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int tp_monitor_debugfs_init(struct wlan_ptracker_core *core) { struct wlan_ptracker_debugfs *debugfs = &core->debugfs; struct tp_monitor_stats *stats = &core->tp; struct wlan_ptracker_client *client = core->client; stats->dir = debugfs_create_dir(client->ifname, debugfs->root); if (!stats->dir) return -ENODEV; debugfs_create_u32("log_level", 0600, stats->dir, &stats->debug); debugfs_create_file("tx", 0400, stats->dir, &stats->tx, &counter_ops); debugfs_create_file("rx", 0400, stats->dir, &stats->rx, &counter_ops); return 0; } int tp_monitor_init(struct tp_monitor_stats *stats) { struct wlan_ptracker_core *core = tp_to_core(stats); struct net *net = dev_net(core->dev); int err = 0; int i; /* debugfs */ tp_monitor_debugfs_init(core); /* assign net_device for ingress check and filter */ for (i = 0 ; i < WLAN_PTRACKER_NF_LEN; i++) { wlan_ptracker_nfops[i].dev = core->dev; wlan_ptracker_nfops[i].priv = core; } /* register hook function to netfilter */ err = nf_register_net_hooks(net, wlan_ptracker_nfops, WLAN_PTRACKER_NF_LEN); if (err) goto out; /* start a timer to update rate and pps */ tp_timer_start(stats); return 0; out: ptracker_err(core, "initial err (%d)\n", err); return err; } void tp_monitor_exit(struct tp_monitor_stats *stats) { struct wlan_ptracker_core *core = tp_to_core(stats); struct net *net = dev_net(core->dev); if (stats->dir) debugfs_remove_recursive(stats->dir); tp_timer_stop(stats); nf_unregister_net_hooks(net, wlan_ptracker_nfops, WLAN_PTRACKER_NF_LEN); }