]> git.itanic.dy.fi Git - linux-stable/blob - drivers/net/netdevsim/hwstats.c
netdevsim: Fix hwstats debugfs file permissions
[linux-stable] / 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         const struct file_operations fops;
335         enum nsim_dev_hwstats_do action;
336         enum netdev_offload_xstats_type type;
337 };
338
339 static ssize_t
340 nsim_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(data, count, 0, &ifindex);
354         if (err)
355                 return err;
356
357         hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, 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                                                        hwsfops->type,
365                                                        hwsdev_list);
366                 break;
367         case NSIM_DEV_HWSTATS_DO_ENABLE:
368                 err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,
369                                                       hwsfops->type,
370                                                       hwsdev_list);
371                 break;
372         case NSIM_DEV_HWSTATS_DO_FAIL:
373                 err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,
374                                                     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
396 static 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
400 static 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
404 static 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
410 int 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(&hwstats->l3_list);
418
419         hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;
420         err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);
421         if (err)
422                 goto err_mutex_destroy;
423
424         hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);
425         if (IS_ERR(hwstats->ddir)) {
426                 err = PTR_ERR(hwstats->ddir);
427                 goto err_unregister_notifier;
428         }
429
430         hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);
431         if (IS_ERR(hwstats->l3_ddir)) {
432                 err = PTR_ERR(hwstats->l3_ddir);
433                 goto err_remove_hwstats_recursive;
434         }
435
436         debugfs_create_file("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,
437                             &nsim_dev_hwstats_l3_enable_fops.fops);
438         debugfs_create_file("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,
439                             &nsim_dev_hwstats_l3_disable_fops.fops);
440         debugfs_create_file("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,
441                             &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(&hwstats->traffic_dw,
446                               msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
447         return 0;
448
449 err_remove_hwstats_recursive:
450         debugfs_remove_recursive(hwstats->ddir);
451 err_unregister_notifier:
452         unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
453 err_mutex_destroy:
454         mutex_destroy(&hwstats->hwsdev_list_lock);
455         return err;
456 }
457
458 static 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(&hwsdev->list);
471                 nsim_dev_hwsdev_fini(hwsdev);
472         }
473         mutex_unlock(&hwstats->hwsdev_list_lock);
474 }
475
476 void 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(&hwstats->traffic_dw);
482         debugfs_remove_recursive(hwstats->ddir);
483         unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
484         nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
485         mutex_destroy(&hwstats->hwsdev_list_lock);
486 }