]> Git Repo - linux.git/blob - drivers/net/netdevsim/hwstats.c
Merge tag 'riscv-for-linus-6.14-mw1' of git://git.kernel.org/pub/scm/linux/kernel...
[linux.git] / drivers / net / netdevsim / hwstats.c
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 }
This page took 0.052869 seconds and 4 git commands to generate.