blob: 3937506cf016ecbe5b809488cf68ed793d87197a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Wifi performance tracker
*
* Copyright 2022 Google LLC.
*
* Author: Star Chang <starchang@google.com>
*/
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/debugfs.h>
#include <net/dsfield.h>
#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);
}