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, 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
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(info->report_delta, &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(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, 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
140out:
141	mutex_unlock(&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(hwsdev->netdev);
148	kfree(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, dev->ifindex);
164	if (!hwsdev)
165		return;
166
167	list_del(&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					    NETDEV_OFFLOAD_XSTATS_TYPE_L3);
177	mutex_unlock(&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(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(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
262out_put_netdev:
263	dev_put(netdev);
264out_unlock_list:
265	mutex_unlock(&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(&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
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(&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(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
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(&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
449err_remove_hwstats_recursive:
450	debugfs_remove_recursive(hwstats->ddir);
451err_unregister_notifier:
452	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
453err_mutex_destroy:
454	mutex_destroy(&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(&hwsdev->list);
471		nsim_dev_hwsdev_fini(hwsdev);
472	}
473	mutex_unlock(&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(&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