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 struct net *net; 224 int err = 0; 225 226 nsim_dev = container_of(hwstats, struct nsim_dev, hwstats); 227 net = nsim_dev_net(nsim_dev); 228 229 rtnl_lock(); 230 mutex_lock(&hwstats->hwsdev_list_lock); 231 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex); 232 if (hwsdev) 233 goto out_unlock_list; 234 235 netdev = dev_get_by_index(net, ifindex); 236 if (!netdev) { 237 err = -ENODEV; 238 goto out_unlock_list; 239 } 240 241 hwsdev = kzalloc(sizeof(*hwsdev), GFP_KERNEL); 242 if (!hwsdev) { 243 err = -ENOMEM; 244 goto out_put_netdev; 245 } 246 247 hwsdev->netdev = netdev; 248 list_add_tail(&hwsdev->list, hwsdev_list); 249 mutex_unlock(&hwstats->hwsdev_list_lock); 250 251 if (netdev_offload_xstats_enabled(netdev, type)) { 252 nsim_dev_hwsdev_enable(hwsdev, NULL); 253 rtnl_offload_xstats_notify(netdev); 254 } 255 256 rtnl_unlock(); 257 return err; 258 259 out_put_netdev: 260 dev_put(netdev); 261 out_unlock_list: 262 mutex_unlock(&hwstats->hwsdev_list_lock); 263 rtnl_unlock(); 264 return err; 265 } 266 267 static int 268 nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats, 269 int ifindex, 270 enum netdev_offload_xstats_type type, 271 struct list_head *hwsdev_list) 272 { 273 struct nsim_dev_hwstats_netdev *hwsdev; 274 int err = 0; 275 276 rtnl_lock(); 277 mutex_lock(&hwstats->hwsdev_list_lock); 278 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex); 279 if (hwsdev) 280 list_del(&hwsdev->list); 281 mutex_unlock(&hwstats->hwsdev_list_lock); 282 283 if (!hwsdev) { 284 err = -ENOENT; 285 goto unlock_out; 286 } 287 288 if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) { 289 netdev_offload_xstats_push_delta(hwsdev->netdev, type, 290 &hwsdev->stats); 291 rtnl_offload_xstats_notify(hwsdev->netdev); 292 } 293 nsim_dev_hwsdev_fini(hwsdev); 294 295 unlock_out: 296 rtnl_unlock(); 297 return err; 298 } 299 300 static int 301 nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats, 302 int ifindex, 303 enum netdev_offload_xstats_type type, 304 struct list_head *hwsdev_list) 305 { 306 struct nsim_dev_hwstats_netdev *hwsdev; 307 int err = 0; 308 309 mutex_lock(&hwstats->hwsdev_list_lock); 310 311 hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex); 312 if (!hwsdev) { 313 err = -ENOENT; 314 goto err_hwsdev_list_unlock; 315 } 316 317 hwsdev->fail_enable = true; 318 319 err_hwsdev_list_unlock: 320 mutex_unlock(&hwstats->hwsdev_list_lock); 321 return err; 322 } 323 324 enum nsim_dev_hwstats_do { 325 NSIM_DEV_HWSTATS_DO_DISABLE, 326 NSIM_DEV_HWSTATS_DO_ENABLE, 327 NSIM_DEV_HWSTATS_DO_FAIL, 328 }; 329 330 struct nsim_dev_hwstats_fops { 331 enum nsim_dev_hwstats_do action; 332 enum netdev_offload_xstats_type type; 333 }; 334 335 static ssize_t 336 nsim_dev_hwstats_do_write(struct file *file, 337 const char __user *data, 338 size_t count, loff_t *ppos) 339 { 340 struct nsim_dev_hwstats *hwstats = file->private_data; 341 const struct nsim_dev_hwstats_fops *hwsfops; 342 struct list_head *hwsdev_list; 343 int ifindex; 344 int err; 345 346 hwsfops = debugfs_get_aux(file); 347 348 err = kstrtoint_from_user(data, count, 0, &ifindex); 349 if (err) 350 return err; 351 352 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type); 353 if (WARN_ON(!hwsdev_list)) 354 return -EINVAL; 355 356 switch (hwsfops->action) { 357 case NSIM_DEV_HWSTATS_DO_DISABLE: 358 err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex, 359 hwsfops->type, 360 hwsdev_list); 361 break; 362 case NSIM_DEV_HWSTATS_DO_ENABLE: 363 err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex, 364 hwsfops->type, 365 hwsdev_list); 366 break; 367 case NSIM_DEV_HWSTATS_DO_FAIL: 368 err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex, 369 hwsfops->type, 370 hwsdev_list); 371 break; 372 } 373 if (err) 374 return err; 375 376 return count; 377 } 378 379 static struct debugfs_short_fops debugfs_ops = { 380 .write = nsim_dev_hwstats_do_write, 381 .llseek = generic_file_llseek, 382 }; 383 384 #define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE) \ 385 { \ 386 .action = ACTION, \ 387 .type = TYPE, \ 388 } 389 390 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops = 391 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE, 392 NETDEV_OFFLOAD_XSTATS_TYPE_L3); 393 394 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops = 395 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE, 396 NETDEV_OFFLOAD_XSTATS_TYPE_L3); 397 398 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops = 399 NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL, 400 NETDEV_OFFLOAD_XSTATS_TYPE_L3); 401 402 #undef NSIM_DEV_HWSTATS_FOPS 403 404 int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev) 405 { 406 struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats; 407 struct net *net = nsim_dev_net(nsim_dev); 408 int err; 409 410 mutex_init(&hwstats->hwsdev_list_lock); 411 INIT_LIST_HEAD(&hwstats->l3_list); 412 413 hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event; 414 err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb); 415 if (err) 416 goto err_mutex_destroy; 417 418 hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir); 419 if (IS_ERR(hwstats->ddir)) { 420 err = PTR_ERR(hwstats->ddir); 421 goto err_unregister_notifier; 422 } 423 424 hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir); 425 if (IS_ERR(hwstats->l3_ddir)) { 426 err = PTR_ERR(hwstats->l3_ddir); 427 goto err_remove_hwstats_recursive; 428 } 429 430 debugfs_create_file_aux("enable_ifindex", 0200, hwstats->l3_ddir, hwstats, 431 &nsim_dev_hwstats_l3_enable_fops, &debugfs_ops); 432 debugfs_create_file_aux("disable_ifindex", 0200, hwstats->l3_ddir, hwstats, 433 &nsim_dev_hwstats_l3_disable_fops, &debugfs_ops); 434 debugfs_create_file_aux("fail_next_enable", 0200, hwstats->l3_ddir, hwstats, 435 &nsim_dev_hwstats_l3_fail_fops, &debugfs_ops); 436 437 INIT_DELAYED_WORK(&hwstats->traffic_dw, 438 &nsim_dev_hwstats_traffic_work); 439 schedule_delayed_work(&hwstats->traffic_dw, 440 msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS)); 441 return 0; 442 443 err_remove_hwstats_recursive: 444 debugfs_remove_recursive(hwstats->ddir); 445 err_unregister_notifier: 446 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb); 447 err_mutex_destroy: 448 mutex_destroy(&hwstats->hwsdev_list_lock); 449 return err; 450 } 451 452 static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats, 453 enum netdev_offload_xstats_type type) 454 { 455 struct nsim_dev_hwstats_netdev *hwsdev, *tmp; 456 struct list_head *hwsdev_list; 457 458 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type); 459 if (WARN_ON(!hwsdev_list)) 460 return; 461 462 mutex_lock(&hwstats->hwsdev_list_lock); 463 list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) { 464 list_del(&hwsdev->list); 465 nsim_dev_hwsdev_fini(hwsdev); 466 } 467 mutex_unlock(&hwstats->hwsdev_list_lock); 468 } 469 470 void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev) 471 { 472 struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats; 473 struct net *net = nsim_dev_net(nsim_dev); 474 475 cancel_delayed_work_sync(&hwstats->traffic_dw); 476 debugfs_remove_recursive(hwstats->ddir); 477 unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb); 478 nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3); 479 mutex_destroy(&hwstats->hwsdev_list_lock); 480 } 481