// SPDX-License-Identifier: GPL-2.0 #include <linux/debugfs.h> #include "netdevsim.h" #define NSIM_DEV_HWSTATS_TRAFFIC_MS 100 static struct list_head *nsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats *hwstats, enum netdev_offload_xstats_type type){ switch (type) { case NETDEV_OFFLOAD_XSTATS_TYPE_L3: return &hwstats->l3_list; } WARN_ON_ONCE(1); return NULL;} static void nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats *hwstats, enum netdev_offload_xstats_type type){ struct nsim_dev_hwstats_netdev *hwsdev; struct list_head *hwsdev_list; hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type); if (WARN_ON(!hwsdev_list)) return; list_for_each_entry(hwsdev, hwsdev_list, list) { if (hwsdev->enabled) { hwsdev->stats.rx_packets += 1; hwsdev->stats.tx_packets += 2; hwsdev->stats.rx_bytes += 100; hwsdev->stats.tx_bytes += 300; } }} static void nsim_dev_hwstats_traffic_work(struct work_struct *work){ struct nsim_dev_hwstats *hwstats; hwstats = container_of(work, struct nsim_dev_hwstats, traffic_dw.work); mutex_lock(&hwstats->hwsdev_list_lock); nsim_dev_hwstats_traffic_bump(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3); mutex_unlock(&hwstats->hwsdev_list_lock); schedule_delayed_work(&hwstats->traffic_dw, msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));} static struct nsim_dev_hwstats_netdev *nsim_dev_hwslist_find_hwsdev(struct list_head *hwsdev_list, int ifindex){ struct nsim_dev_hwstats_netdev *hwsdev; list_for_each_entry(hwsdev, hwsdev_list, list) { if (hwsdev->netdev->ifindex == ifindex) return hwsdev; } return NULL;} static int nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev *hwsdev, struct netlink_ext_ack *extack){ if (hwsdev->fail_enable) { hwsdev->fail_enable = false; NL_SET_ERR_MSG_MOD(extack, "Stats enablement set to fail"); return -ECANCELED; } hwsdev->enabled = true; return 0;} static void nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev *hwsdev){ hwsdev->enabled = false; memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));} static intnsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev *hwsdev, struct netdev_notifier_offload_xstats_info *info){ netdev_offload_xstats_report_delta(info->report_delta, &hwsdev->stats); memset(&hwsdev->stats, 0, sizeof(hwsdev->stats)); return 0;} static voidnsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev *hwsdev, struct netdev_notifier_offload_xstats_info *info){ if (hwsdev->enabled) netdev_offload_xstats_report_used(info->report_used);} static int nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats *hwstats, struct net_device *dev, unsigned long event, void *ptr){ struct netdev_notifier_offload_xstats_info *info; struct nsim_dev_hwstats_netdev *hwsdev; struct list_head *hwsdev_list; int err = 0; info = ptr; hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, info->type); if (!hwsdev_list) return 0; mutex_lock(&hwstats->hwsdev_list_lock); hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex); if (!hwsdev) goto out; switch (event) { case NETDEV_OFFLOAD_XSTATS_ENABLE: err = nsim_dev_hwsdev_enable(hwsdev, info->info.extack); break; case NETDEV_OFFLOAD_XSTATS_DISABLE: nsim_dev_hwsdev_disable(hwsdev); break; case NETDEV_OFFLOAD_XSTATS_REPORT_USED: nsim_dev_hwsdev_report_used(hwsdev, info); break; case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA: err = nsim_dev_hwsdev_report_delta(hwsdev, info); break; } out: mutex_unlock(&hwstats->hwsdev_list_lock); return err;} static void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev){ dev_put(hwsdev->netdev); kfree(hwsdev);} static void__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats, struct net_device *dev, enum netdev_offload_xstats_type type){ struct nsim_dev_hwstats_netdev *hwsdev; struct list_head *hwsdev_list; hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type); if (WARN_ON(!hwsdev_list)) return; hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex); if (!hwsdev) return; list_del(&hwsdev->list); nsim_dev_hwsdev_fini(hwsdev);} static void nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats, struct net_device *dev){ mutex_lock(&hwstats->hwsdev_list_lock); __nsim_dev_hwstats_event_unregister(hwstats, dev, NETDEV_OFFLOAD_XSTATS_TYPE_L3); mutex_unlock(&hwstats->hwsdev_list_lock);} static int nsim_dev_hwstats_event(struct nsim_dev_hwstats *hwstats, struct net_device *dev, unsigned long event, void *ptr){ switch (event) { case NETDEV_OFFLOAD_XSTATS_ENABLE: case NETDEV_OFFLOAD_XSTATS_DISABLE: case NETDEV_OFFLOAD_XSTATS_REPORT_USED: case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA: return nsim_dev_hwstats_event_off_xstats(hwstats, dev, event, ptr); case NETDEV_UNREGISTER: nsim_dev_hwstats_event_unregister(hwstats, dev); break; } return 0;} static int nsim_dev_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr){ struct net_device *dev = netdev_notifier_info_to_dev(ptr); struct nsim_dev_hwstats *hwstats; int err = 0; hwstats = container_of(nb, struct nsim_dev_hwstats, netdevice_nb); err = nsim_dev_hwstats_event(hwstats, dev, event, ptr); if (err) return notifier_from_errno(err); return NOTIFY_OK;} static intnsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats *hwstats, int ifindex, enum netdev_offload_xstats_type type, struct list_head *hwsdev_list){ struct nsim_dev_hwstats_netdev *hwsdev; struct nsim_dev *nsim_dev; struct net_device *netdev; bool notify = false; struct net *net; int err = 0; nsim_dev = container_of(hwstats, struct nsim_dev, hwstats); net = nsim_dev_net(nsim_dev); rtnl_lock(); mutex_lock(&hwstats->hwsdev_list_lock); hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex); if (hwsdev) goto out_unlock_list; netdev = dev_get_by_index(net, ifindex); if (!netdev) { err = -ENODEV; goto out_unlock_list; } hwsdev = kzalloc(sizeof(*hwsdev), GFP_KERNEL); if (!hwsdev) { err = -ENOMEM; goto out_put_netdev; } hwsdev->netdev = netdev; list_add_tail(&hwsdev->list, hwsdev_list); mutex_unlock(&hwstats->hwsdev_list_lock); if (netdev_offload_xstats_enabled(netdev, type)) { nsim_dev_hwsdev_enable(hwsdev, NULL); notify = true; } if (notify) rtnl_offload_xstats_notify(netdev); rtnl_unlock(); return err; out_put_netdev: dev_put(netdev);out_unlock_list: mutex_unlock(&hwstats->hwsdev_list_lock); rtnl_unlock(); return err;} static intnsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats, int ifindex, enum netdev_offload_xstats_type type, struct list_head *hwsdev_list){ struct nsim_dev_hwstats_netdev *hwsdev; int err = 0; rtnl_lock(); mutex_lock(&hwstats->hwsdev_list_lock); hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex); if (hwsdev) list_del(&hwsdev->list); mutex_unlock(&hwstats->hwsdev_list_lock); if (!hwsdev) { err = -ENOENT; goto unlock_out; } if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) { netdev_offload_xstats_push_delta(hwsdev->netdev, type, &hwsdev->stats); rtnl_offload_xstats_notify(hwsdev->netdev); } nsim_dev_hwsdev_fini(hwsdev); unlock_out: rtnl_unlock(); return err;} static intnsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats, int ifindex, enum netdev_offload_xstats_type type, struct list_head *hwsdev_list){ struct nsim_dev_hwstats_netdev *hwsdev; int err = 0; mutex_lock(&hwstats->hwsdev_list_lock); hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex); if (!hwsdev) { err = -ENOENT; goto err_hwsdev_list_unlock; } hwsdev->fail_enable = true; err_hwsdev_list_unlock: mutex_unlock(&hwstats->hwsdev_list_lock); return err;} enum nsim_dev_hwstats_do { NSIM_DEV_HWSTATS_DO_DISABLE, NSIM_DEV_HWSTATS_DO_ENABLE, NSIM_DEV_HWSTATS_DO_FAIL,}; struct nsim_dev_hwstats_fops { const struct file_operations fops; enum nsim_dev_hwstats_do action; enum netdev_offload_xstats_type type;}; static ssize_tnsim_dev_hwstats_do_write(struct file *file, const char __user *data, size_t count, loff_t *ppos){ struct nsim_dev_hwstats *hwstats = file->private_data; struct nsim_dev_hwstats_fops *hwsfops; struct list_head *hwsdev_list; int ifindex; int err; hwsfops = container_of(debugfs_real_fops(file), struct nsim_dev_hwstats_fops, fops); err = kstrtoint_from_user(data, count, 0, &ifindex); if (err) return err; hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type); if (WARN_ON(!hwsdev_list)) return -EINVAL; switch (hwsfops->action) { case NSIM_DEV_HWSTATS_DO_DISABLE: err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex, hwsfops->type, hwsdev_list); break; case NSIM_DEV_HWSTATS_DO_ENABLE: err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex, hwsfops->type, hwsdev_list); break; case NSIM_DEV_HWSTATS_DO_FAIL: err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex, hwsfops->type, hwsdev_list); break; } if (err) return err; return count;} #define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE) \ { \ .fops = { \ .open = simple_open, \ .write = nsim_dev_hwstats_do_write, \ .llseek = generic_file_llseek, \ .owner = THIS_MODULE, \ }, \ .action = ACTION, \ .type = TYPE, \ } static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops = NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE, NETDEV_OFFLOAD_XSTATS_TYPE_L3); static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops = NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE, NETDEV_OFFLOAD_XSTATS_TYPE_L3); static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops = NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL, NETDEV_OFFLOAD_XSTATS_TYPE_L3); #undef NSIM_DEV_HWSTATS_FOPS int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev){ struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats; struct net *net = nsim_dev_net(nsim_dev); int err; mutex_init(&hwstats->hwsdev_list_lock); INIT_LIST_HEAD(&hwstats->l3_list); hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event; err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb); if (err) goto err_mutex_destroy; hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir); if (IS_ERR(hwstats->ddir)) { err = PTR_ERR(hwstats->ddir); goto err_unregister_notifier; } hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir); if (IS_ERR(hwstats->l3_ddir)) { err = PTR_ERR(hwstats->l3_ddir); goto err_remove_hwstats_recursive; } debugfs_create_file("enable_ifindex", 0200, hwstats->l3_ddir, hwstats, &nsim_dev_hwstats_l3_enable_fops.fops); debugfs_create_file("disable_ifindex", 0200, hwstats->l3_ddir, hwstats, &nsim_dev_hwstats_l3_disable_fops.fops); debugfs_create_file("fail_next_enable", 0200, hwstats->l3_ddir, hwstats, &nsim_dev_hwstats_l3_fail_fops.fops); INIT_DELAYED_WORK(&hwstats->traffic_dw, &nsim_dev_hwstats_traffic_work); schedule_delayed_work(&hwstats->traffic_dw, msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS)); return 0; err_remove_hwstats_recursive: debugfs_remove_recursive(hwstats->ddir);err_unregister_notifier: unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);err_mutex_destroy: mutex_destroy(&hwstats->hwsdev_list_lock); return err;} static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats, enum netdev_offload_xstats_type type){ struct nsim_dev_hwstats_netdev *hwsdev, *tmp; struct list_head *hwsdev_list; hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type); if (WARN_ON(!hwsdev_list)) return; mutex_lock(&hwstats->hwsdev_list_lock); list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) { list_del(&hwsdev->list); nsim_dev_hwsdev_fini(hwsdev); } mutex_unlock(&hwstats->hwsdev_list_lock);} void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev){ struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats; struct net *net = nsim_dev_net(nsim_dev); cancel_delayed_work_sync(&hwstats->traffic_dw); debugfs_remove_recursive(hwstats->ddir); unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb); nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3); mutex_destroy(&hwstats->hwsdev_list_lock);}
// SPDX-License-Identifier: GPL-2.0
#include <linux/debugfs.h>
#include "netdevsim.h"
#define NSIM_DEV_HWSTATS_TRAFFIC_MS 100
static struct list_head *
nsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats *hwstats,
enum netdev_offload_xstats_type type)
{
switch (type) {
case NETDEV_OFFLOAD_XSTATS_TYPE_L3:
return &hwstats->l3_list;
}
WARN_ON_ONCE(1);
return NULL;
}
static void nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats *hwstats,
enum netdev_offload_xstats_type type)
{
struct nsim_dev_hwstats_netdev *hwsdev;
struct list_head *hwsdev_list;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
if (WARN_ON(!hwsdev_list))
return;
list_for_each_entry(hwsdev, hwsdev_list, list) {
if (hwsdev->enabled) {
hwsdev->stats.rx_packets += 1;
hwsdev->stats.tx_packets += 2;
hwsdev->stats.rx_bytes += 100;
hwsdev->stats.tx_bytes += 300;
}
}
}
static void nsim_dev_hwstats_traffic_work(struct work_struct *work)
{
struct nsim_dev_hwstats *hwstats;
hwstats = container_of(work, struct nsim_dev_hwstats, traffic_dw.work);
mutex_lock(&hwstats->hwsdev_list_lock);
nsim_dev_hwstats_traffic_bump(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
mutex_unlock(&hwstats->hwsdev_list_lock);
schedule_delayed_work(&hwstats->traffic_dw,
msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
}
static struct nsim_dev_hwstats_netdev *
nsim_dev_hwslist_find_hwsdev(struct list_head *hwsdev_list,
int ifindex)
{
struct nsim_dev_hwstats_netdev *hwsdev;
list_for_each_entry(hwsdev, hwsdev_list, list) {
if (hwsdev->netdev->ifindex == ifindex)
return hwsdev;
}
return NULL;
}
static int nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev *hwsdev,
struct netlink_ext_ack *extack)
{
if (hwsdev->fail_enable) {
hwsdev->fail_enable = false;
NL_SET_ERR_MSG_MOD(extack, "Stats enablement set to fail");
return -ECANCELED;
}
hwsdev->enabled = true;
return 0;
}
static void nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev *hwsdev)
{
hwsdev->enabled = false;
memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
}
static int
nsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev *hwsdev,
struct netdev_notifier_offload_xstats_info *info)
{
netdev_offload_xstats_report_delta(info->report_delta, &hwsdev->stats);
memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
return 0;
}
static void
nsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev *hwsdev,
struct netdev_notifier_offload_xstats_info *info)
{
if (hwsdev->enabled)
netdev_offload_xstats_report_used(info->report_used);
}
static int nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats *hwstats,
struct net_device *dev,
unsigned long event, void *ptr)
{
struct netdev_notifier_offload_xstats_info *info;
struct nsim_dev_hwstats_netdev *hwsdev;
struct list_head *hwsdev_list;
int err = 0;
info = ptr;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, info->type);
if (!hwsdev_list)
return 0;
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);
if (!hwsdev)
goto out;
switch (event) {
case NETDEV_OFFLOAD_XSTATS_ENABLE:
err = nsim_dev_hwsdev_enable(hwsdev, info->info.extack);
break;
case NETDEV_OFFLOAD_XSTATS_DISABLE:
nsim_dev_hwsdev_disable(hwsdev);
break;
case NETDEV_OFFLOAD_XSTATS_REPORT_USED:
nsim_dev_hwsdev_report_used(hwsdev, info);
break;
case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:
err = nsim_dev_hwsdev_report_delta(hwsdev, info);
break;
}
out:
mutex_unlock(&hwstats->hwsdev_list_lock);
return err;
}
static void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev)
{
dev_put(hwsdev->netdev);
kfree(hwsdev);
}
static void
__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,
struct net_device *dev,
enum netdev_offload_xstats_type type)
{
struct nsim_dev_hwstats_netdev *hwsdev;
struct list_head *hwsdev_list;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
if (WARN_ON(!hwsdev_list))
return;
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);
if (!hwsdev)
return;
list_del(&hwsdev->list);
nsim_dev_hwsdev_fini(hwsdev);
}
static void nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,
struct net_device *dev)
{
mutex_lock(&hwstats->hwsdev_list_lock);
__nsim_dev_hwstats_event_unregister(hwstats, dev,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
mutex_unlock(&hwstats->hwsdev_list_lock);
}
static int nsim_dev_hwstats_event(struct nsim_dev_hwstats *hwstats,
struct net_device *dev,
unsigned long event, void *ptr)
{
switch (event) {
case NETDEV_OFFLOAD_XSTATS_ENABLE:
case NETDEV_OFFLOAD_XSTATS_DISABLE:
case NETDEV_OFFLOAD_XSTATS_REPORT_USED:
case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:
return nsim_dev_hwstats_event_off_xstats(hwstats, dev,
event, ptr);
case NETDEV_UNREGISTER:
nsim_dev_hwstats_event_unregister(hwstats, dev);
break;
}
return 0;
}
static int nsim_dev_netdevice_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct nsim_dev_hwstats *hwstats;
int err = 0;
hwstats = container_of(nb, struct nsim_dev_hwstats, netdevice_nb);
err = nsim_dev_hwstats_event(hwstats, dev, event, ptr);
if (err)
return notifier_from_errno(err);
return NOTIFY_OK;
}
static int
nsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats *hwstats,
int ifindex,
enum netdev_offload_xstats_type type,
struct list_head *hwsdev_list)
{
struct nsim_dev_hwstats_netdev *hwsdev;
struct nsim_dev *nsim_dev;
struct net_device *netdev;
bool notify = false;
struct net *net;
int err = 0;
nsim_dev = container_of(hwstats, struct nsim_dev, hwstats);
net = nsim_dev_net(nsim_dev);
rtnl_lock();
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
if (hwsdev)
goto out_unlock_list;
netdev = dev_get_by_index(net, ifindex);
if (!netdev) {
err = -ENODEV;
goto out_unlock_list;
}
hwsdev = kzalloc(sizeof(*hwsdev), GFP_KERNEL);
if (!hwsdev) {
err = -ENOMEM;
goto out_put_netdev;
}
hwsdev->netdev = netdev;
list_add_tail(&hwsdev->list, hwsdev_list);
mutex_unlock(&hwstats->hwsdev_list_lock);
if (netdev_offload_xstats_enabled(netdev, type)) {
nsim_dev_hwsdev_enable(hwsdev, NULL);
notify = true;
}
if (notify)
rtnl_offload_xstats_notify(netdev);
rtnl_unlock();
return err;
out_put_netdev:
dev_put(netdev);
out_unlock_list:
mutex_unlock(&hwstats->hwsdev_list_lock);
rtnl_unlock();
return err;
}
static int
nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats,
int ifindex,
enum netdev_offload_xstats_type type,
struct list_head *hwsdev_list)
{
struct nsim_dev_hwstats_netdev *hwsdev;
int err = 0;
rtnl_lock();
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
if (hwsdev)
list_del(&hwsdev->list);
mutex_unlock(&hwstats->hwsdev_list_lock);
if (!hwsdev) {
err = -ENOENT;
goto unlock_out;
}
if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) {
netdev_offload_xstats_push_delta(hwsdev->netdev, type,
&hwsdev->stats);
rtnl_offload_xstats_notify(hwsdev->netdev);
}
nsim_dev_hwsdev_fini(hwsdev);
unlock_out:
rtnl_unlock();
return err;
}
static int
nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats,
int ifindex,
enum netdev_offload_xstats_type type,
struct list_head *hwsdev_list)
{
struct nsim_dev_hwstats_netdev *hwsdev;
int err = 0;
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
if (!hwsdev) {
err = -ENOENT;
goto err_hwsdev_list_unlock;
}
hwsdev->fail_enable = true;
err_hwsdev_list_unlock:
mutex_unlock(&hwstats->hwsdev_list_lock);
return err;
}
enum nsim_dev_hwstats_do {
NSIM_DEV_HWSTATS_DO_DISABLE,
NSIM_DEV_HWSTATS_DO_ENABLE,
NSIM_DEV_HWSTATS_DO_FAIL,
};
struct nsim_dev_hwstats_fops {
const struct file_operations fops;
enum nsim_dev_hwstats_do action;
enum netdev_offload_xstats_type type;
};
static ssize_t
nsim_dev_hwstats_do_write(struct file *file,
const char __user *data,
size_t count, loff_t *ppos)
{
struct nsim_dev_hwstats *hwstats = file->private_data;
struct nsim_dev_hwstats_fops *hwsfops;
struct list_head *hwsdev_list;
int ifindex;
int err;
hwsfops = container_of(debugfs_real_fops(file),
struct nsim_dev_hwstats_fops, fops);
err = kstrtoint_from_user(data, count, 0, &ifindex);
if (err)
return err;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type);
if (WARN_ON(!hwsdev_list))
return -EINVAL;
switch (hwsfops->action) {
case NSIM_DEV_HWSTATS_DO_DISABLE:
err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex,
hwsfops->type,
hwsdev_list);
break;
case NSIM_DEV_HWSTATS_DO_ENABLE:
err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,
hwsfops->type,
hwsdev_list);
break;
case NSIM_DEV_HWSTATS_DO_FAIL:
err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,
hwsfops->type,
hwsdev_list);
break;
}
if (err)
return err;
return count;
}
#define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE) \
{ \
.fops = { \
.open = simple_open, \
.write = nsim_dev_hwstats_do_write, \
.llseek = generic_file_llseek, \
.owner = THIS_MODULE, \
}, \
.action = ACTION, \
.type = TYPE, \
}
static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops =
NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops =
NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops =
NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
#undef NSIM_DEV_HWSTATS_FOPS
int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev)
{
struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
struct net *net = nsim_dev_net(nsim_dev);
int err;
mutex_init(&hwstats->hwsdev_list_lock);
INIT_LIST_HEAD(&hwstats->l3_list);
hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;
err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);
if (err)
goto err_mutex_destroy;
hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);
if (IS_ERR(hwstats->ddir)) {
err = PTR_ERR(hwstats->ddir);
goto err_unregister_notifier;
}
hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);
if (IS_ERR(hwstats->l3_ddir)) {
err = PTR_ERR(hwstats->l3_ddir);
goto err_remove_hwstats_recursive;
}
debugfs_create_file("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,
&nsim_dev_hwstats_l3_enable_fops.fops);
debugfs_create_file("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,
&nsim_dev_hwstats_l3_disable_fops.fops);
debugfs_create_file("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,
&nsim_dev_hwstats_l3_fail_fops.fops);
INIT_DELAYED_WORK(&hwstats->traffic_dw,
&nsim_dev_hwstats_traffic_work);
schedule_delayed_work(&hwstats->traffic_dw,
msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
return 0;
err_remove_hwstats_recursive:
debugfs_remove_recursive(hwstats->ddir);
err_unregister_notifier:
unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
err_mutex_destroy:
mutex_destroy(&hwstats->hwsdev_list_lock);
return err;
}
static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats,
enum netdev_offload_xstats_type type)
{
struct nsim_dev_hwstats_netdev *hwsdev, *tmp;
struct list_head *hwsdev_list;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
if (WARN_ON(!hwsdev_list))
return;
mutex_lock(&hwstats->hwsdev_list_lock);
list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) {
list_del(&hwsdev->list);
nsim_dev_hwsdev_fini(hwsdev);
}
mutex_unlock(&hwstats->hwsdev_list_lock);
}
void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev)
{
struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
struct net *net = nsim_dev_net(nsim_dev);
cancel_delayed_work_sync(&hwstats->traffic_dw);
debugfs_remove_recursive(hwstats->ddir);
unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
mutex_destroy(&hwstats->hwsdev_list_lock);
}