File Index
Files and directories
Sign in
FILE

hwstats.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/netdevsim/hwstats.c C
  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	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
259out_put_netdev:
260	dev_put(netdev);
261out_unlock_list:
262	mutex_unlock(&hwstats->hwsdev_list_lock);
263	rtnl_unlock();
264	return err;
265}
266
267static int
268nsim_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
295unlock_out:
296	rtnl_unlock();
297	return err;
298}
299
300static int
301nsim_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
319err_hwsdev_list_unlock:
320	mutex_unlock(&hwstats->hwsdev_list_lock);
321	return err;
322}
323
324enum nsim_dev_hwstats_do {
325	NSIM_DEV_HWSTATS_DO_DISABLE,
326	NSIM_DEV_HWSTATS_DO_ENABLE,
327	NSIM_DEV_HWSTATS_DO_FAIL,
328};
329
330struct nsim_dev_hwstats_fops {
331	enum nsim_dev_hwstats_do action;
332	enum netdev_offload_xstats_type type;
333};
334
335static ssize_t
336nsim_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
379static 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
390static 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
394static 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
398static 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
404int 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
443err_remove_hwstats_recursive:
444	debugfs_remove_recursive(hwstats->ddir);
445err_unregister_notifier:
446	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
447err_mutex_destroy:
448	mutex_destroy(&hwstats->hwsdev_list_lock);
449	return err;
450}
451
452static 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
470void 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}
~/ Read-only file preview.