xref: /openbmc/linux/drivers/net/netdevsim/hwstats.c (revision f5c27da4)
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 }
487