xref: /openbmc/linux/drivers/net/netdevsim/hwstats.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
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  	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
nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats * hwstats,int ifindex,enum netdev_offload_xstats_type type,struct list_head * hwsdev_list)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
nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats * hwstats,int ifindex,enum netdev_offload_xstats_type type,struct list_head * hwsdev_list)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
nsim_dev_hwstats_do_write(struct file * file,const char __user * data,size_t count,loff_t * ppos)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  
nsim_dev_hwstats_init(struct nsim_dev * nsim_dev)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  
nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats * hwstats,enum netdev_offload_xstats_type type)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  
nsim_dev_hwstats_exit(struct nsim_dev * nsim_dev)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  }
487