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 enum nsim_dev_hwstats_do action; 335 enum netdev_offload_xstats_type type; 336 }; 337 338 static ssize_t 339 nsim_dev_hwstats_do_write(struct file *file, 340 const char __user *data, 341 size_t count, loff_t *ppos) 342 { 343 struct nsim_dev_hwstats *hwstats = file->private_data; 344 const struct nsim_dev_hwstats_fops *hwsfops; 345 struct list_head *hwsdev_list; 346 int ifindex; 347 int err; 348 349 hwsfops = debugfs_get_aux(file); 350 351 err = kstrtoint_from_user(data, count, 0, &ifindex); 352 if (err) 353 return err; 354 355 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type); 356 if (WARN_ON(!hwsdev_list)) 357 return -EINVAL; 358 359 switch (hwsfops->action) { 360 case NSIM_DEV_HWSTATS_DO_DISABLE: 361 err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex, 362 hwsfops->type, 363 hwsdev_list); 364 break; 365 case NSIM_DEV_HWSTATS_DO_ENABLE: 366 err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex, 367 hwsfops->type, 368 hwsdev_list); 369 break; 370 case NSIM_DEV_HWSTATS_DO_FAIL: 371 err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex, 372 hwsfops->type, 373 hwsdev_list); 374 break; 375 } 376 if (err) 377 return err; 378 379 return count; 380 } 381 382 static struct debugfs_short_fops debugfs_ops = { 383 .write = nsim_dev_hwstats_do_write, 384 .llseek = generic_file_llseek, 385 }; 386 387 #define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE) \ 388 { \ 389 .action = ACTION, \ 390 .type = TYPE, \ 391 } 392 393 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops = 394 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE, 395 NETDEV_OFFLOAD_XSTATS_TYPE_L3); 396 397 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops = 398 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE, 399 NETDEV_OFFLOAD_XSTATS_TYPE_L3); 400 401 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops = 402 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL, 403 NETDEV_OFFLOAD_XSTATS_TYPE_L3); 404 405 #undef NSIM_DEV_HWSTATS_FOPS 406 407 int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev) 408 { 409 struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats; 410 struct net *net = nsim_dev_net(nsim_dev); 411 int err; 412 413 mutex_init(&hwstats->hwsdev_list_lock); 414 INIT_LIST_HEAD(&hwstats->l3_list); 415 416 hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event; 417 err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb); 418 if (err) 419 goto err_mutex_destroy; 420 421 hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir); 422 if (IS_ERR(hwstats->ddir)) { 423 err = PTR_ERR(hwstats->ddir); 424 goto err_unregister_notifier; 425 } 426 427 hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir); 428 if (IS_ERR(hwstats->l3_ddir)) { 429 err = PTR_ERR(hwstats->l3_ddir); 430 goto err_remove_hwstats_recursive; 431 } 432 433 debugfs_create_file_aux("enable_ifindex", 0200, hwstats->l3_ddir, hwstats, 434 &nsim_dev_hwstats_l3_enable_fops, &debugfs_ops); 435 debugfs_create_file_aux("disable_ifindex", 0200, hwstats->l3_ddir, hwstats, 436 &nsim_dev_hwstats_l3_disable_fops, &debugfs_ops); 437 debugfs_create_file_aux("fail_next_enable", 0200, hwstats->l3_ddir, hwstats, 438 &nsim_dev_hwstats_l3_fail_fops, &debugfs_ops); 439 440 INIT_DELAYED_WORK(&hwstats->traffic_dw, 441 &nsim_dev_hwstats_traffic_work); 442 schedule_delayed_work(&hwstats->traffic_dw, 443 msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS)); 444 return 0; 445 446 err_remove_hwstats_recursive: 447 debugfs_remove_recursive(hwstats->ddir); 448 err_unregister_notifier: 449 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb); 450 err_mutex_destroy: 451 mutex_destroy(&hwstats->hwsdev_list_lock); 452 return err; 453 } 454 455 static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats, 456 enum netdev_offload_xstats_type type) 457 { 458 struct nsim_dev_hwstats_netdev *hwsdev, *tmp; 459 struct list_head *hwsdev_list; 460 461 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type); 462 if (WARN_ON(!hwsdev_list)) 463 return; 464 465 mutex_lock(&hwstats->hwsdev_list_lock); 466 list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) { 467 list_del(&hwsdev->list); 468 nsim_dev_hwsdev_fini(hwsdev); 469 } 470 mutex_unlock(&hwstats->hwsdev_list_lock); 471 } 472 473 void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev) 474 { 475 struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats; 476 struct net *net = nsim_dev_net(nsim_dev); 477 478 cancel_delayed_work_sync(&hwstats->traffic_dw); 479 debugfs_remove_recursive(hwstats->ddir); 480 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb); 481 nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3); 482 mutex_destroy(&hwstats->hwsdev_list_lock); 483 } 484