1 // SPDX-License-Identifier: GPL-2.0 2 3 #include <linux/debugfs.h> 4 5 #include "netdevsim.h" 6 7 #define NSIM_DEV_HWSTATS_TRAFFIC_MS 100 8 9 static struct list_head * 10 nsim_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 22 static 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 42 static 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 55 static struct nsim_dev_hwstats_netdev * 56 nsim_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 69 static 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 82 static 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 88 static int 89 nsim_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 97 static void 98 nsim_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 105 static 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 140 out: 141 mutex_unlock(&hwstats->hwsdev_list_lock); 142 return err; 143 } 144 145 static void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev) 146 { 147 dev_put(hwsdev->netdev); 148 kfree(hwsdev); 149 } 150 151 static void 152 __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 171 static 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 180 static 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 199 static 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 214 static int 215 nsim_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 262 out_put_netdev: 263 dev_put(netdev); 264 out_unlock_list: 265 mutex_unlock(&hwstats->hwsdev_list_lock); 266 rtnl_unlock(); 267 return err; 268 } 269 270 static int 271 nsim_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 298 unlock_out: 299 rtnl_unlock(); 300 return err; 301 } 302 303 static int 304 nsim_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 322 err_hwsdev_list_unlock: 323 mutex_unlock(&hwstats->hwsdev_list_lock); 324 return err; 325 } 326 327 enum nsim_dev_hwstats_do { 328 NSIM_DEV_HWSTATS_DO_DISABLE, 329 NSIM_DEV_HWSTATS_DO_ENABLE, 330 NSIM_DEV_HWSTATS_DO_FAIL, 331 }; 332 333 struct 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 339 static ssize_t 340 nsim_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 396 static 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 400 static 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 404 static 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_FOPS 409 410 int 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 449 err_remove_hwstats_recursive: 450 debugfs_remove_recursive(hwstats->ddir); 451 err_unregister_notifier: 452 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb); 453 err_mutex_destroy: 454 mutex_destroy(&hwstats->hwsdev_list_lock); 455 return err; 456 } 457 458 static 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 476 void 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