xref: /linux/drivers/net/netdevsim/hwstats.c (revision 8be4d31cb8aaeea27bde4b7ddb26e28a89062ebf)
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 	struct net *net;
224 	int err = 0;
225 
226 	nsim_dev = container_of(hwstats, struct nsim_dev, hwstats);
227 	net = nsim_dev_net(nsim_dev);
228 
229 	rtnl_lock();
230 	mutex_lock(&hwstats->hwsdev_list_lock);
231 	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
232 	if (hwsdev)
233 		goto out_unlock_list;
234 
235 	netdev = dev_get_by_index(net, ifindex);
236 	if (!netdev) {
237 		err = -ENODEV;
238 		goto out_unlock_list;
239 	}
240 
241 	hwsdev = kzalloc(sizeof(*hwsdev), GFP_KERNEL);
242 	if (!hwsdev) {
243 		err = -ENOMEM;
244 		goto out_put_netdev;
245 	}
246 
247 	hwsdev->netdev = netdev;
248 	list_add_tail(&hwsdev->list, hwsdev_list);
249 	mutex_unlock(&hwstats->hwsdev_list_lock);
250 
251 	if (netdev_offload_xstats_enabled(netdev, type)) {
252 		nsim_dev_hwsdev_enable(hwsdev, NULL);
253 		rtnl_offload_xstats_notify(netdev);
254 	}
255 
256 	rtnl_unlock();
257 	return err;
258 
259 out_put_netdev:
260 	dev_put(netdev);
261 out_unlock_list:
262 	mutex_unlock(&hwstats->hwsdev_list_lock);
263 	rtnl_unlock();
264 	return err;
265 }
266 
267 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)268 nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats,
269 				 int ifindex,
270 				 enum netdev_offload_xstats_type type,
271 				 struct list_head *hwsdev_list)
272 {
273 	struct nsim_dev_hwstats_netdev *hwsdev;
274 	int err = 0;
275 
276 	rtnl_lock();
277 	mutex_lock(&hwstats->hwsdev_list_lock);
278 	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
279 	if (hwsdev)
280 		list_del(&hwsdev->list);
281 	mutex_unlock(&hwstats->hwsdev_list_lock);
282 
283 	if (!hwsdev) {
284 		err = -ENOENT;
285 		goto unlock_out;
286 	}
287 
288 	if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) {
289 		netdev_offload_xstats_push_delta(hwsdev->netdev, type,
290 						 &hwsdev->stats);
291 		rtnl_offload_xstats_notify(hwsdev->netdev);
292 	}
293 	nsim_dev_hwsdev_fini(hwsdev);
294 
295 unlock_out:
296 	rtnl_unlock();
297 	return err;
298 }
299 
300 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)301 nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats,
302 			      int ifindex,
303 			      enum netdev_offload_xstats_type type,
304 			      struct list_head *hwsdev_list)
305 {
306 	struct nsim_dev_hwstats_netdev *hwsdev;
307 	int err = 0;
308 
309 	mutex_lock(&hwstats->hwsdev_list_lock);
310 
311 	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
312 	if (!hwsdev) {
313 		err = -ENOENT;
314 		goto err_hwsdev_list_unlock;
315 	}
316 
317 	hwsdev->fail_enable = true;
318 
319 err_hwsdev_list_unlock:
320 	mutex_unlock(&hwstats->hwsdev_list_lock);
321 	return err;
322 }
323 
324 enum nsim_dev_hwstats_do {
325 	NSIM_DEV_HWSTATS_DO_DISABLE,
326 	NSIM_DEV_HWSTATS_DO_ENABLE,
327 	NSIM_DEV_HWSTATS_DO_FAIL,
328 };
329 
330 struct nsim_dev_hwstats_fops {
331 	enum nsim_dev_hwstats_do action;
332 	enum netdev_offload_xstats_type type;
333 };
334 
335 static ssize_t
nsim_dev_hwstats_do_write(struct file * file,const char __user * data,size_t count,loff_t * ppos)336 nsim_dev_hwstats_do_write(struct file *file,
337 			  const char __user *data,
338 			  size_t count, loff_t *ppos)
339 {
340 	struct nsim_dev_hwstats *hwstats = file->private_data;
341 	const struct nsim_dev_hwstats_fops *hwsfops;
342 	struct list_head *hwsdev_list;
343 	int ifindex;
344 	int err;
345 
346 	hwsfops = debugfs_get_aux(file);
347 
348 	err = kstrtoint_from_user(data, count, 0, &ifindex);
349 	if (err)
350 		return err;
351 
352 	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type);
353 	if (WARN_ON(!hwsdev_list))
354 		return -EINVAL;
355 
356 	switch (hwsfops->action) {
357 	case NSIM_DEV_HWSTATS_DO_DISABLE:
358 		err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex,
359 						       hwsfops->type,
360 						       hwsdev_list);
361 		break;
362 	case NSIM_DEV_HWSTATS_DO_ENABLE:
363 		err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,
364 						      hwsfops->type,
365 						      hwsdev_list);
366 		break;
367 	case NSIM_DEV_HWSTATS_DO_FAIL:
368 		err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,
369 						    hwsfops->type,
370 						    hwsdev_list);
371 		break;
372 	}
373 	if (err)
374 		return err;
375 
376 	return count;
377 }
378 
379 static struct debugfs_short_fops debugfs_ops = {
380 	.write = nsim_dev_hwstats_do_write,
381 	.llseek = generic_file_llseek,
382 };
383 
384 #define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE)			\
385 	{							\
386 		.action = ACTION,				\
387 		.type = TYPE,					\
388 	}
389 
390 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops =
391 	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE,
392 			      NETDEV_OFFLOAD_XSTATS_TYPE_L3);
393 
394 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops =
395 	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE,
396 			      NETDEV_OFFLOAD_XSTATS_TYPE_L3);
397 
398 static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops =
399 	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL,
400 			      NETDEV_OFFLOAD_XSTATS_TYPE_L3);
401 
402 #undef NSIM_DEV_HWSTATS_FOPS
403 
nsim_dev_hwstats_init(struct nsim_dev * nsim_dev)404 int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev)
405 {
406 	struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
407 	struct net *net = nsim_dev_net(nsim_dev);
408 	int err;
409 
410 	mutex_init(&hwstats->hwsdev_list_lock);
411 	INIT_LIST_HEAD(&hwstats->l3_list);
412 
413 	hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;
414 	err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);
415 	if (err)
416 		goto err_mutex_destroy;
417 
418 	hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);
419 	if (IS_ERR(hwstats->ddir)) {
420 		err = PTR_ERR(hwstats->ddir);
421 		goto err_unregister_notifier;
422 	}
423 
424 	hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);
425 	if (IS_ERR(hwstats->l3_ddir)) {
426 		err = PTR_ERR(hwstats->l3_ddir);
427 		goto err_remove_hwstats_recursive;
428 	}
429 
430 	debugfs_create_file_aux("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,
431 			    &nsim_dev_hwstats_l3_enable_fops, &debugfs_ops);
432 	debugfs_create_file_aux("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,
433 			    &nsim_dev_hwstats_l3_disable_fops, &debugfs_ops);
434 	debugfs_create_file_aux("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,
435 			    &nsim_dev_hwstats_l3_fail_fops, &debugfs_ops);
436 
437 	INIT_DELAYED_WORK(&hwstats->traffic_dw,
438 			  &nsim_dev_hwstats_traffic_work);
439 	schedule_delayed_work(&hwstats->traffic_dw,
440 			      msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
441 	return 0;
442 
443 err_remove_hwstats_recursive:
444 	debugfs_remove_recursive(hwstats->ddir);
445 err_unregister_notifier:
446 	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
447 err_mutex_destroy:
448 	mutex_destroy(&hwstats->hwsdev_list_lock);
449 	return err;
450 }
451 
nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats * hwstats,enum netdev_offload_xstats_type type)452 static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats,
453 				      enum netdev_offload_xstats_type type)
454 {
455 	struct nsim_dev_hwstats_netdev *hwsdev, *tmp;
456 	struct list_head *hwsdev_list;
457 
458 	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
459 	if (WARN_ON(!hwsdev_list))
460 		return;
461 
462 	mutex_lock(&hwstats->hwsdev_list_lock);
463 	list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) {
464 		list_del(&hwsdev->list);
465 		nsim_dev_hwsdev_fini(hwsdev);
466 	}
467 	mutex_unlock(&hwstats->hwsdev_list_lock);
468 }
469 
nsim_dev_hwstats_exit(struct nsim_dev * nsim_dev)470 void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev)
471 {
472 	struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
473 	struct net *net = nsim_dev_net(nsim_dev);
474 
475 	cancel_delayed_work_sync(&hwstats->traffic_dw);
476 	debugfs_remove_recursive(hwstats->ddir);
477 	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
478 	nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
479 	mutex_destroy(&hwstats->hwsdev_list_lock);
480 }
481