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 *
nsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats * hwstats,enum netdev_offload_xstats_type type)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
nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats * hwstats,enum netdev_offload_xstats_type type)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
nsim_dev_hwstats_traffic_work(struct work_struct * work)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 *
nsim_dev_hwslist_find_hwsdev(struct list_head * hwsdev_list,int ifindex)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
nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev * hwsdev,struct netlink_ext_ack * extack)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
nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev * hwsdev)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
nsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev * hwsdev,struct netdev_notifier_offload_xstats_info * info)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
nsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev * hwsdev,struct netdev_notifier_offload_xstats_info * info)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
nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats * hwstats,struct net_device * dev,unsigned long event,void * ptr)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
nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev * hwsdev)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
__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats * hwstats,struct net_device * dev,enum netdev_offload_xstats_type type)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
nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats * hwstats,struct net_device * dev)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
nsim_dev_hwstats_event(struct nsim_dev_hwstats * hwstats,struct net_device * dev,unsigned long event,void * ptr)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
nsim_dev_netdevice_event(struct notifier_block * nb,unsigned long event,void * ptr)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
nsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats * hwstats,int ifindex,enum netdev_offload_xstats_type type,struct list_head * hwsdev_list)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
nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats * hwstats,int ifindex,enum netdev_offload_xstats_type type,struct list_head * hwsdev_list)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
nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats * hwstats,int ifindex,enum netdev_offload_xstats_type type,struct list_head * hwsdev_list)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
nsim_dev_hwstats_do_write(struct file * file,const char __user * data,size_t count,loff_t * ppos)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
nsim_dev_hwstats_init(struct nsim_dev * nsim_dev)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
nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats * hwstats,enum netdev_offload_xstats_type type)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
nsim_dev_hwstats_exit(struct nsim_dev * nsim_dev)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