487 lines · c
1// SPDX-License-Identifier: GPL-2.02 3#include <linux/debugfs.h>4 5#include "netdevsim.h"6 7#define NSIM_DEV_HWSTATS_TRAFFIC_MS 1008 9static struct list_head *10nsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats *hwstats,11 enum netdev_offload_xstats_type type)12{13 switch (type) {14 case NETDEV_OFFLOAD_XSTATS_TYPE_L3:15 return &hwstats->l3_list;16 }17 18 WARN_ON_ONCE(1);19 return NULL;20}21 22static void nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats *hwstats,23 enum netdev_offload_xstats_type type)24{25 struct nsim_dev_hwstats_netdev *hwsdev;26 struct list_head *hwsdev_list;27 28 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);29 if (WARN_ON(!hwsdev_list))30 return;31 32 list_for_each_entry(hwsdev, hwsdev_list, list) {33 if (hwsdev->enabled) {34 hwsdev->stats.rx_packets += 1;35 hwsdev->stats.tx_packets += 2;36 hwsdev->stats.rx_bytes += 100;37 hwsdev->stats.tx_bytes += 300;38 }39 }40}41 42static void nsim_dev_hwstats_traffic_work(struct work_struct *work)43{44 struct nsim_dev_hwstats *hwstats;45 46 hwstats = container_of(work, struct nsim_dev_hwstats, traffic_dw.work);47 mutex_lock(&hwstats->hwsdev_list_lock);48 nsim_dev_hwstats_traffic_bump(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);49 mutex_unlock(&hwstats->hwsdev_list_lock);50 51 schedule_delayed_work(&hwstats->traffic_dw,52 msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));53}54 55static struct nsim_dev_hwstats_netdev *56nsim_dev_hwslist_find_hwsdev(struct list_head *hwsdev_list,57 int ifindex)58{59 struct nsim_dev_hwstats_netdev *hwsdev;60 61 list_for_each_entry(hwsdev, hwsdev_list, list) {62 if (hwsdev->netdev->ifindex == ifindex)63 return hwsdev;64 }65 66 return NULL;67}68 69static int nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev *hwsdev,70 struct netlink_ext_ack *extack)71{72 if (hwsdev->fail_enable) {73 hwsdev->fail_enable = false;74 NL_SET_ERR_MSG_MOD(extack, "Stats enablement set to fail");75 return -ECANCELED;76 }77 78 hwsdev->enabled = true;79 return 0;80}81 82static void nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev *hwsdev)83{84 hwsdev->enabled = false;85 memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));86}87 88static int89nsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev *hwsdev,90 struct netdev_notifier_offload_xstats_info *info)91{92 netdev_offload_xstats_report_delta(info->report_delta, &hwsdev->stats);93 memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));94 return 0;95}96 97static void98nsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev *hwsdev,99 struct netdev_notifier_offload_xstats_info *info)100{101 if (hwsdev->enabled)102 netdev_offload_xstats_report_used(info->report_used);103}104 105static int nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats *hwstats,106 struct net_device *dev,107 unsigned long event, void *ptr)108{109 struct netdev_notifier_offload_xstats_info *info;110 struct nsim_dev_hwstats_netdev *hwsdev;111 struct list_head *hwsdev_list;112 int err = 0;113 114 info = ptr;115 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, info->type);116 if (!hwsdev_list)117 return 0;118 119 mutex_lock(&hwstats->hwsdev_list_lock);120 121 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);122 if (!hwsdev)123 goto out;124 125 switch (event) {126 case NETDEV_OFFLOAD_XSTATS_ENABLE:127 err = nsim_dev_hwsdev_enable(hwsdev, info->info.extack);128 break;129 case NETDEV_OFFLOAD_XSTATS_DISABLE:130 nsim_dev_hwsdev_disable(hwsdev);131 break;132 case NETDEV_OFFLOAD_XSTATS_REPORT_USED:133 nsim_dev_hwsdev_report_used(hwsdev, info);134 break;135 case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:136 err = nsim_dev_hwsdev_report_delta(hwsdev, info);137 break;138 }139 140out:141 mutex_unlock(&hwstats->hwsdev_list_lock);142 return err;143}144 145static void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev)146{147 dev_put(hwsdev->netdev);148 kfree(hwsdev);149}150 151static void152__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,153 struct net_device *dev,154 enum netdev_offload_xstats_type type)155{156 struct nsim_dev_hwstats_netdev *hwsdev;157 struct list_head *hwsdev_list;158 159 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);160 if (WARN_ON(!hwsdev_list))161 return;162 163 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);164 if (!hwsdev)165 return;166 167 list_del(&hwsdev->list);168 nsim_dev_hwsdev_fini(hwsdev);169}170 171static void nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,172 struct net_device *dev)173{174 mutex_lock(&hwstats->hwsdev_list_lock);175 __nsim_dev_hwstats_event_unregister(hwstats, dev,176 NETDEV_OFFLOAD_XSTATS_TYPE_L3);177 mutex_unlock(&hwstats->hwsdev_list_lock);178}179 180static int nsim_dev_hwstats_event(struct nsim_dev_hwstats *hwstats,181 struct net_device *dev,182 unsigned long event, void *ptr)183{184 switch (event) {185 case NETDEV_OFFLOAD_XSTATS_ENABLE:186 case NETDEV_OFFLOAD_XSTATS_DISABLE:187 case NETDEV_OFFLOAD_XSTATS_REPORT_USED:188 case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:189 return nsim_dev_hwstats_event_off_xstats(hwstats, dev,190 event, ptr);191 case NETDEV_UNREGISTER:192 nsim_dev_hwstats_event_unregister(hwstats, dev);193 break;194 }195 196 return 0;197}198 199static int nsim_dev_netdevice_event(struct notifier_block *nb,200 unsigned long event, void *ptr)201{202 struct net_device *dev = netdev_notifier_info_to_dev(ptr);203 struct nsim_dev_hwstats *hwstats;204 int err = 0;205 206 hwstats = container_of(nb, struct nsim_dev_hwstats, netdevice_nb);207 err = nsim_dev_hwstats_event(hwstats, dev, event, ptr);208 if (err)209 return notifier_from_errno(err);210 211 return NOTIFY_OK;212}213 214static int215nsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats *hwstats,216 int ifindex,217 enum netdev_offload_xstats_type type,218 struct list_head *hwsdev_list)219{220 struct nsim_dev_hwstats_netdev *hwsdev;221 struct nsim_dev *nsim_dev;222 struct net_device *netdev;223 bool notify = false;224 struct net *net;225 int err = 0;226 227 nsim_dev = container_of(hwstats, struct nsim_dev, hwstats);228 net = nsim_dev_net(nsim_dev);229 230 rtnl_lock();231 mutex_lock(&hwstats->hwsdev_list_lock);232 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);233 if (hwsdev)234 goto out_unlock_list;235 236 netdev = dev_get_by_index(net, ifindex);237 if (!netdev) {238 err = -ENODEV;239 goto out_unlock_list;240 }241 242 hwsdev = kzalloc(sizeof(*hwsdev), GFP_KERNEL);243 if (!hwsdev) {244 err = -ENOMEM;245 goto out_put_netdev;246 }247 248 hwsdev->netdev = netdev;249 list_add_tail(&hwsdev->list, hwsdev_list);250 mutex_unlock(&hwstats->hwsdev_list_lock);251 252 if (netdev_offload_xstats_enabled(netdev, type)) {253 nsim_dev_hwsdev_enable(hwsdev, NULL);254 notify = true;255 }256 257 if (notify)258 rtnl_offload_xstats_notify(netdev);259 rtnl_unlock();260 return err;261 262out_put_netdev:263 dev_put(netdev);264out_unlock_list:265 mutex_unlock(&hwstats->hwsdev_list_lock);266 rtnl_unlock();267 return err;268}269 270static int271nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats,272 int ifindex,273 enum netdev_offload_xstats_type type,274 struct list_head *hwsdev_list)275{276 struct nsim_dev_hwstats_netdev *hwsdev;277 int err = 0;278 279 rtnl_lock();280 mutex_lock(&hwstats->hwsdev_list_lock);281 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);282 if (hwsdev)283 list_del(&hwsdev->list);284 mutex_unlock(&hwstats->hwsdev_list_lock);285 286 if (!hwsdev) {287 err = -ENOENT;288 goto unlock_out;289 }290 291 if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) {292 netdev_offload_xstats_push_delta(hwsdev->netdev, type,293 &hwsdev->stats);294 rtnl_offload_xstats_notify(hwsdev->netdev);295 }296 nsim_dev_hwsdev_fini(hwsdev);297 298unlock_out:299 rtnl_unlock();300 return err;301}302 303static int304nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats,305 int ifindex,306 enum netdev_offload_xstats_type type,307 struct list_head *hwsdev_list)308{309 struct nsim_dev_hwstats_netdev *hwsdev;310 int err = 0;311 312 mutex_lock(&hwstats->hwsdev_list_lock);313 314 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);315 if (!hwsdev) {316 err = -ENOENT;317 goto err_hwsdev_list_unlock;318 }319 320 hwsdev->fail_enable = true;321 322err_hwsdev_list_unlock:323 mutex_unlock(&hwstats->hwsdev_list_lock);324 return err;325}326 327enum nsim_dev_hwstats_do {328 NSIM_DEV_HWSTATS_DO_DISABLE,329 NSIM_DEV_HWSTATS_DO_ENABLE,330 NSIM_DEV_HWSTATS_DO_FAIL,331};332 333struct nsim_dev_hwstats_fops {334 const struct file_operations fops;335 enum nsim_dev_hwstats_do action;336 enum netdev_offload_xstats_type type;337};338 339static ssize_t340nsim_dev_hwstats_do_write(struct file *file,341 const char __user *data,342 size_t count, loff_t *ppos)343{344 struct nsim_dev_hwstats *hwstats = file->private_data;345 struct nsim_dev_hwstats_fops *hwsfops;346 struct list_head *hwsdev_list;347 int ifindex;348 int err;349 350 hwsfops = container_of(debugfs_real_fops(file),351 struct nsim_dev_hwstats_fops, fops);352 353 err = kstrtoint_from_user(data, count, 0, &ifindex);354 if (err)355 return err;356 357 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type);358 if (WARN_ON(!hwsdev_list))359 return -EINVAL;360 361 switch (hwsfops->action) {362 case NSIM_DEV_HWSTATS_DO_DISABLE:363 err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex,364 hwsfops->type,365 hwsdev_list);366 break;367 case NSIM_DEV_HWSTATS_DO_ENABLE:368 err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,369 hwsfops->type,370 hwsdev_list);371 break;372 case NSIM_DEV_HWSTATS_DO_FAIL:373 err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,374 hwsfops->type,375 hwsdev_list);376 break;377 }378 if (err)379 return err;380 381 return count;382}383 384#define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE) \385 { \386 .fops = { \387 .open = simple_open, \388 .write = nsim_dev_hwstats_do_write, \389 .llseek = generic_file_llseek, \390 .owner = THIS_MODULE, \391 }, \392 .action = ACTION, \393 .type = TYPE, \394 }395 396static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops =397 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE,398 NETDEV_OFFLOAD_XSTATS_TYPE_L3);399 400static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops =401 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE,402 NETDEV_OFFLOAD_XSTATS_TYPE_L3);403 404static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops =405 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL,406 NETDEV_OFFLOAD_XSTATS_TYPE_L3);407 408#undef NSIM_DEV_HWSTATS_FOPS409 410int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev)411{412 struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;413 struct net *net = nsim_dev_net(nsim_dev);414 int err;415 416 mutex_init(&hwstats->hwsdev_list_lock);417 INIT_LIST_HEAD(&hwstats->l3_list);418 419 hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;420 err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);421 if (err)422 goto err_mutex_destroy;423 424 hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);425 if (IS_ERR(hwstats->ddir)) {426 err = PTR_ERR(hwstats->ddir);427 goto err_unregister_notifier;428 }429 430 hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);431 if (IS_ERR(hwstats->l3_ddir)) {432 err = PTR_ERR(hwstats->l3_ddir);433 goto err_remove_hwstats_recursive;434 }435 436 debugfs_create_file("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,437 &nsim_dev_hwstats_l3_enable_fops.fops);438 debugfs_create_file("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,439 &nsim_dev_hwstats_l3_disable_fops.fops);440 debugfs_create_file("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,441 &nsim_dev_hwstats_l3_fail_fops.fops);442 443 INIT_DELAYED_WORK(&hwstats->traffic_dw,444 &nsim_dev_hwstats_traffic_work);445 schedule_delayed_work(&hwstats->traffic_dw,446 msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));447 return 0;448 449err_remove_hwstats_recursive:450 debugfs_remove_recursive(hwstats->ddir);451err_unregister_notifier:452 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);453err_mutex_destroy:454 mutex_destroy(&hwstats->hwsdev_list_lock);455 return err;456}457 458static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats,459 enum netdev_offload_xstats_type type)460{461 struct nsim_dev_hwstats_netdev *hwsdev, *tmp;462 struct list_head *hwsdev_list;463 464 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);465 if (WARN_ON(!hwsdev_list))466 return;467 468 mutex_lock(&hwstats->hwsdev_list_lock);469 list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) {470 list_del(&hwsdev->list);471 nsim_dev_hwsdev_fini(hwsdev);472 }473 mutex_unlock(&hwstats->hwsdev_list_lock);474}475 476void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev)477{478 struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;479 struct net *net = nsim_dev_net(nsim_dev);480 481 cancel_delayed_work_sync(&hwstats->traffic_dw);482 debugfs_remove_recursive(hwstats->ddir);483 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);484 nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);485 mutex_destroy(&hwstats->hwsdev_list_lock);486}487