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
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, type: NETDEV_OFFLOAD_XSTATS_TYPE_L3);
49 mutex_unlock(lock: &hwstats->hwsdev_list_lock);
50
51 schedule_delayed_work(dwork: &hwstats->traffic_dw,
52 delay: 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 int
89nsim_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(rd: info->report_delta, stats: &hwsdev->stats);
93 memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
94 return 0;
95}
96
97static void
98nsim_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(ru: 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, type: 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, ifindex: 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, extack: 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(lock: &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(dev: hwsdev->netdev);
148 kfree(objp: hwsdev);
149}
150
151static 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, ifindex: dev->ifindex);
164 if (!hwsdev)
165 return;
166
167 list_del(entry: &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 type: NETDEV_OFFLOAD_XSTATS_TYPE_L3);
177 mutex_unlock(lock: &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(info: 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 int
215nsim_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(size: 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(new: &hwsdev->list, head: hwsdev_list);
250 mutex_unlock(lock: &hwstats->hwsdev_list_lock);
251
252 if (netdev_offload_xstats_enabled(dev: netdev, type)) {
253 nsim_dev_hwsdev_enable(hwsdev, NULL);
254 notify = true;
255 }
256
257 if (notify)
258 rtnl_offload_xstats_notify(dev: netdev);
259 rtnl_unlock();
260 return err;
261
262out_put_netdev:
263 dev_put(dev: netdev);
264out_unlock_list:
265 mutex_unlock(lock: &hwstats->hwsdev_list_lock);
266 rtnl_unlock();
267 return err;
268}
269
270static int
271nsim_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(entry: &hwsdev->list);
284 mutex_unlock(lock: &hwstats->hwsdev_list_lock);
285
286 if (!hwsdev) {
287 err = -ENOENT;
288 goto unlock_out;
289 }
290
291 if (netdev_offload_xstats_enabled(dev: hwsdev->netdev, type)) {
292 netdev_offload_xstats_push_delta(dev: hwsdev->netdev, type,
293 stats: &hwsdev->stats);
294 rtnl_offload_xstats_notify(dev: hwsdev->netdev);
295 }
296 nsim_dev_hwsdev_fini(hwsdev);
297
298unlock_out:
299 rtnl_unlock();
300 return err;
301}
302
303static int
304nsim_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(lock: &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_t
340nsim_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(s: data, count, base: 0, res: &ifindex);
354 if (err)
355 return err;
356
357 hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type: 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 type: hwsfops->type,
365 hwsdev_list);
366 break;
367 case NSIM_DEV_HWSTATS_DO_ENABLE:
368 err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,
369 type: hwsfops->type,
370 hwsdev_list);
371 break;
372 case NSIM_DEV_HWSTATS_DO_FAIL:
373 err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,
374 type: 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_FOPS
409
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(list: &hwstats->l3_list);
418
419 hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;
420 err = register_netdevice_notifier_net(net, nb: &hwstats->netdevice_nb);
421 if (err)
422 goto err_mutex_destroy;
423
424 hwstats->ddir = debugfs_create_dir(name: "hwstats", parent: nsim_dev->ddir);
425 if (IS_ERR(ptr: hwstats->ddir)) {
426 err = PTR_ERR(ptr: hwstats->ddir);
427 goto err_unregister_notifier;
428 }
429
430 hwstats->l3_ddir = debugfs_create_dir(name: "l3", parent: hwstats->ddir);
431 if (IS_ERR(ptr: hwstats->l3_ddir)) {
432 err = PTR_ERR(ptr: hwstats->l3_ddir);
433 goto err_remove_hwstats_recursive;
434 }
435
436 debugfs_create_file(name: "enable_ifindex", mode: 0200, parent: hwstats->l3_ddir, data: hwstats,
437 fops: &nsim_dev_hwstats_l3_enable_fops.fops);
438 debugfs_create_file(name: "disable_ifindex", mode: 0200, parent: hwstats->l3_ddir, data: hwstats,
439 fops: &nsim_dev_hwstats_l3_disable_fops.fops);
440 debugfs_create_file(name: "fail_next_enable", mode: 0200, parent: hwstats->l3_ddir, data: hwstats,
441 fops: &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(dwork: &hwstats->traffic_dw,
446 delay: msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
447 return 0;
448
449err_remove_hwstats_recursive:
450 debugfs_remove_recursive(dentry: hwstats->ddir);
451err_unregister_notifier:
452 unregister_netdevice_notifier_net(net, nb: &hwstats->netdevice_nb);
453err_mutex_destroy:
454 mutex_destroy(lock: &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(entry: &hwsdev->list);
471 nsim_dev_hwsdev_fini(hwsdev);
472 }
473 mutex_unlock(lock: &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(dwork: &hwstats->traffic_dw);
482 debugfs_remove_recursive(dentry: hwstats->ddir);
483 unregister_netdevice_notifier_net(net, nb: &hwstats->netdevice_nb);
484 nsim_dev_hwsdev_list_wipe(hwstats, type: NETDEV_OFFLOAD_XSTATS_TYPE_L3);
485 mutex_destroy(lock: &hwstats->hwsdev_list_lock);
486}
487

source code of linux/drivers/net/netdevsim/hwstats.c