xref: /linux/drivers/net/netdevsim/hwstats.c (revision 2ab002c755bfa88777e3f2db884d531f3010736c)
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 }
484