162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include <linux/debugfs.h>
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci#include "netdevsim.h"
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define NSIM_DEV_HWSTATS_TRAFFIC_MS	100
862306a36Sopenharmony_ci
962306a36Sopenharmony_cistatic struct list_head *
1062306a36Sopenharmony_cinsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats *hwstats,
1162306a36Sopenharmony_ci			       enum netdev_offload_xstats_type type)
1262306a36Sopenharmony_ci{
1362306a36Sopenharmony_ci	switch (type) {
1462306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_TYPE_L3:
1562306a36Sopenharmony_ci		return &hwstats->l3_list;
1662306a36Sopenharmony_ci	}
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	WARN_ON_ONCE(1);
1962306a36Sopenharmony_ci	return NULL;
2062306a36Sopenharmony_ci}
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic void nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats *hwstats,
2362306a36Sopenharmony_ci					  enum netdev_offload_xstats_type type)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
2662306a36Sopenharmony_ci	struct list_head *hwsdev_list;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
2962306a36Sopenharmony_ci	if (WARN_ON(!hwsdev_list))
3062306a36Sopenharmony_ci		return;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	list_for_each_entry(hwsdev, hwsdev_list, list) {
3362306a36Sopenharmony_ci		if (hwsdev->enabled) {
3462306a36Sopenharmony_ci			hwsdev->stats.rx_packets += 1;
3562306a36Sopenharmony_ci			hwsdev->stats.tx_packets += 2;
3662306a36Sopenharmony_ci			hwsdev->stats.rx_bytes += 100;
3762306a36Sopenharmony_ci			hwsdev->stats.tx_bytes += 300;
3862306a36Sopenharmony_ci		}
3962306a36Sopenharmony_ci	}
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void nsim_dev_hwstats_traffic_work(struct work_struct *work)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct nsim_dev_hwstats *hwstats;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	hwstats = container_of(work, struct nsim_dev_hwstats, traffic_dw.work);
4762306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
4862306a36Sopenharmony_ci	nsim_dev_hwstats_traffic_bump(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
4962306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	schedule_delayed_work(&hwstats->traffic_dw,
5262306a36Sopenharmony_ci			      msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic struct nsim_dev_hwstats_netdev *
5662306a36Sopenharmony_cinsim_dev_hwslist_find_hwsdev(struct list_head *hwsdev_list,
5762306a36Sopenharmony_ci			     int ifindex)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	list_for_each_entry(hwsdev, hwsdev_list, list) {
6262306a36Sopenharmony_ci		if (hwsdev->netdev->ifindex == ifindex)
6362306a36Sopenharmony_ci			return hwsdev;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return NULL;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev *hwsdev,
7062306a36Sopenharmony_ci				  struct netlink_ext_ack *extack)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	if (hwsdev->fail_enable) {
7362306a36Sopenharmony_ci		hwsdev->fail_enable = false;
7462306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Stats enablement set to fail");
7562306a36Sopenharmony_ci		return -ECANCELED;
7662306a36Sopenharmony_ci	}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	hwsdev->enabled = true;
7962306a36Sopenharmony_ci	return 0;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic void nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev *hwsdev)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	hwsdev->enabled = false;
8562306a36Sopenharmony_ci	memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int
8962306a36Sopenharmony_cinsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev *hwsdev,
9062306a36Sopenharmony_ci			     struct netdev_notifier_offload_xstats_info *info)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	netdev_offload_xstats_report_delta(info->report_delta, &hwsdev->stats);
9362306a36Sopenharmony_ci	memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic void
9862306a36Sopenharmony_cinsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev *hwsdev,
9962306a36Sopenharmony_ci			    struct netdev_notifier_offload_xstats_info *info)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	if (hwsdev->enabled)
10262306a36Sopenharmony_ci		netdev_offload_xstats_report_used(info->report_used);
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic int nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats *hwstats,
10662306a36Sopenharmony_ci					     struct net_device *dev,
10762306a36Sopenharmony_ci					     unsigned long event, void *ptr)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct netdev_notifier_offload_xstats_info *info;
11062306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
11162306a36Sopenharmony_ci	struct list_head *hwsdev_list;
11262306a36Sopenharmony_ci	int err = 0;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	info = ptr;
11562306a36Sopenharmony_ci	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, info->type);
11662306a36Sopenharmony_ci	if (!hwsdev_list)
11762306a36Sopenharmony_ci		return 0;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);
12262306a36Sopenharmony_ci	if (!hwsdev)
12362306a36Sopenharmony_ci		goto out;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	switch (event) {
12662306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_ENABLE:
12762306a36Sopenharmony_ci		err = nsim_dev_hwsdev_enable(hwsdev, info->info.extack);
12862306a36Sopenharmony_ci		break;
12962306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_DISABLE:
13062306a36Sopenharmony_ci		nsim_dev_hwsdev_disable(hwsdev);
13162306a36Sopenharmony_ci		break;
13262306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_REPORT_USED:
13362306a36Sopenharmony_ci		nsim_dev_hwsdev_report_used(hwsdev, info);
13462306a36Sopenharmony_ci		break;
13562306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:
13662306a36Sopenharmony_ci		err = nsim_dev_hwsdev_report_delta(hwsdev, info);
13762306a36Sopenharmony_ci		break;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ciout:
14162306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
14262306a36Sopenharmony_ci	return err;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	dev_put(hwsdev->netdev);
14862306a36Sopenharmony_ci	kfree(hwsdev);
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic void
15262306a36Sopenharmony_ci__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,
15362306a36Sopenharmony_ci				    struct net_device *dev,
15462306a36Sopenharmony_ci				    enum netdev_offload_xstats_type type)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
15762306a36Sopenharmony_ci	struct list_head *hwsdev_list;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
16062306a36Sopenharmony_ci	if (WARN_ON(!hwsdev_list))
16162306a36Sopenharmony_ci		return;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);
16462306a36Sopenharmony_ci	if (!hwsdev)
16562306a36Sopenharmony_ci		return;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	list_del(&hwsdev->list);
16862306a36Sopenharmony_ci	nsim_dev_hwsdev_fini(hwsdev);
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic void nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,
17262306a36Sopenharmony_ci					      struct net_device *dev)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
17562306a36Sopenharmony_ci	__nsim_dev_hwstats_event_unregister(hwstats, dev,
17662306a36Sopenharmony_ci					    NETDEV_OFFLOAD_XSTATS_TYPE_L3);
17762306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int nsim_dev_hwstats_event(struct nsim_dev_hwstats *hwstats,
18162306a36Sopenharmony_ci				  struct net_device *dev,
18262306a36Sopenharmony_ci				  unsigned long event, void *ptr)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	switch (event) {
18562306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_ENABLE:
18662306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_DISABLE:
18762306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_REPORT_USED:
18862306a36Sopenharmony_ci	case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:
18962306a36Sopenharmony_ci		return nsim_dev_hwstats_event_off_xstats(hwstats, dev,
19062306a36Sopenharmony_ci							 event, ptr);
19162306a36Sopenharmony_ci	case NETDEV_UNREGISTER:
19262306a36Sopenharmony_ci		nsim_dev_hwstats_event_unregister(hwstats, dev);
19362306a36Sopenharmony_ci		break;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	return 0;
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int nsim_dev_netdevice_event(struct notifier_block *nb,
20062306a36Sopenharmony_ci				    unsigned long event, void *ptr)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
20362306a36Sopenharmony_ci	struct nsim_dev_hwstats *hwstats;
20462306a36Sopenharmony_ci	int err = 0;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	hwstats = container_of(nb, struct nsim_dev_hwstats, netdevice_nb);
20762306a36Sopenharmony_ci	err = nsim_dev_hwstats_event(hwstats, dev, event, ptr);
20862306a36Sopenharmony_ci	if (err)
20962306a36Sopenharmony_ci		return notifier_from_errno(err);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	return NOTIFY_OK;
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic int
21562306a36Sopenharmony_cinsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats *hwstats,
21662306a36Sopenharmony_ci				int ifindex,
21762306a36Sopenharmony_ci				enum netdev_offload_xstats_type type,
21862306a36Sopenharmony_ci				struct list_head *hwsdev_list)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
22162306a36Sopenharmony_ci	struct nsim_dev *nsim_dev;
22262306a36Sopenharmony_ci	struct net_device *netdev;
22362306a36Sopenharmony_ci	bool notify = false;
22462306a36Sopenharmony_ci	struct net *net;
22562306a36Sopenharmony_ci	int err = 0;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	nsim_dev = container_of(hwstats, struct nsim_dev, hwstats);
22862306a36Sopenharmony_ci	net = nsim_dev_net(nsim_dev);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	rtnl_lock();
23162306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
23262306a36Sopenharmony_ci	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
23362306a36Sopenharmony_ci	if (hwsdev)
23462306a36Sopenharmony_ci		goto out_unlock_list;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	netdev = dev_get_by_index(net, ifindex);
23762306a36Sopenharmony_ci	if (!netdev) {
23862306a36Sopenharmony_ci		err = -ENODEV;
23962306a36Sopenharmony_ci		goto out_unlock_list;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	hwsdev = kzalloc(sizeof(*hwsdev), GFP_KERNEL);
24362306a36Sopenharmony_ci	if (!hwsdev) {
24462306a36Sopenharmony_ci		err = -ENOMEM;
24562306a36Sopenharmony_ci		goto out_put_netdev;
24662306a36Sopenharmony_ci	}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	hwsdev->netdev = netdev;
24962306a36Sopenharmony_ci	list_add_tail(&hwsdev->list, hwsdev_list);
25062306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	if (netdev_offload_xstats_enabled(netdev, type)) {
25362306a36Sopenharmony_ci		nsim_dev_hwsdev_enable(hwsdev, NULL);
25462306a36Sopenharmony_ci		notify = true;
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (notify)
25862306a36Sopenharmony_ci		rtnl_offload_xstats_notify(netdev);
25962306a36Sopenharmony_ci	rtnl_unlock();
26062306a36Sopenharmony_ci	return err;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ciout_put_netdev:
26362306a36Sopenharmony_ci	dev_put(netdev);
26462306a36Sopenharmony_ciout_unlock_list:
26562306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
26662306a36Sopenharmony_ci	rtnl_unlock();
26762306a36Sopenharmony_ci	return err;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic int
27162306a36Sopenharmony_cinsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats,
27262306a36Sopenharmony_ci				 int ifindex,
27362306a36Sopenharmony_ci				 enum netdev_offload_xstats_type type,
27462306a36Sopenharmony_ci				 struct list_head *hwsdev_list)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
27762306a36Sopenharmony_ci	int err = 0;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	rtnl_lock();
28062306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
28162306a36Sopenharmony_ci	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
28262306a36Sopenharmony_ci	if (hwsdev)
28362306a36Sopenharmony_ci		list_del(&hwsdev->list);
28462306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (!hwsdev) {
28762306a36Sopenharmony_ci		err = -ENOENT;
28862306a36Sopenharmony_ci		goto unlock_out;
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) {
29262306a36Sopenharmony_ci		netdev_offload_xstats_push_delta(hwsdev->netdev, type,
29362306a36Sopenharmony_ci						 &hwsdev->stats);
29462306a36Sopenharmony_ci		rtnl_offload_xstats_notify(hwsdev->netdev);
29562306a36Sopenharmony_ci	}
29662306a36Sopenharmony_ci	nsim_dev_hwsdev_fini(hwsdev);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ciunlock_out:
29962306a36Sopenharmony_ci	rtnl_unlock();
30062306a36Sopenharmony_ci	return err;
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic int
30462306a36Sopenharmony_cinsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats,
30562306a36Sopenharmony_ci			      int ifindex,
30662306a36Sopenharmony_ci			      enum netdev_offload_xstats_type type,
30762306a36Sopenharmony_ci			      struct list_head *hwsdev_list)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev;
31062306a36Sopenharmony_ci	int err = 0;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
31562306a36Sopenharmony_ci	if (!hwsdev) {
31662306a36Sopenharmony_ci		err = -ENOENT;
31762306a36Sopenharmony_ci		goto err_hwsdev_list_unlock;
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	hwsdev->fail_enable = true;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cierr_hwsdev_list_unlock:
32362306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
32462306a36Sopenharmony_ci	return err;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cienum nsim_dev_hwstats_do {
32862306a36Sopenharmony_ci	NSIM_DEV_HWSTATS_DO_DISABLE,
32962306a36Sopenharmony_ci	NSIM_DEV_HWSTATS_DO_ENABLE,
33062306a36Sopenharmony_ci	NSIM_DEV_HWSTATS_DO_FAIL,
33162306a36Sopenharmony_ci};
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistruct nsim_dev_hwstats_fops {
33462306a36Sopenharmony_ci	const struct file_operations fops;
33562306a36Sopenharmony_ci	enum nsim_dev_hwstats_do action;
33662306a36Sopenharmony_ci	enum netdev_offload_xstats_type type;
33762306a36Sopenharmony_ci};
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_cistatic ssize_t
34062306a36Sopenharmony_cinsim_dev_hwstats_do_write(struct file *file,
34162306a36Sopenharmony_ci			  const char __user *data,
34262306a36Sopenharmony_ci			  size_t count, loff_t *ppos)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct nsim_dev_hwstats *hwstats = file->private_data;
34562306a36Sopenharmony_ci	struct nsim_dev_hwstats_fops *hwsfops;
34662306a36Sopenharmony_ci	struct list_head *hwsdev_list;
34762306a36Sopenharmony_ci	int ifindex;
34862306a36Sopenharmony_ci	int err;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	hwsfops = container_of(debugfs_real_fops(file),
35162306a36Sopenharmony_ci			       struct nsim_dev_hwstats_fops, fops);
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	err = kstrtoint_from_user(data, count, 0, &ifindex);
35462306a36Sopenharmony_ci	if (err)
35562306a36Sopenharmony_ci		return err;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type);
35862306a36Sopenharmony_ci	if (WARN_ON(!hwsdev_list))
35962306a36Sopenharmony_ci		return -EINVAL;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	switch (hwsfops->action) {
36262306a36Sopenharmony_ci	case NSIM_DEV_HWSTATS_DO_DISABLE:
36362306a36Sopenharmony_ci		err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex,
36462306a36Sopenharmony_ci						       hwsfops->type,
36562306a36Sopenharmony_ci						       hwsdev_list);
36662306a36Sopenharmony_ci		break;
36762306a36Sopenharmony_ci	case NSIM_DEV_HWSTATS_DO_ENABLE:
36862306a36Sopenharmony_ci		err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,
36962306a36Sopenharmony_ci						      hwsfops->type,
37062306a36Sopenharmony_ci						      hwsdev_list);
37162306a36Sopenharmony_ci		break;
37262306a36Sopenharmony_ci	case NSIM_DEV_HWSTATS_DO_FAIL:
37362306a36Sopenharmony_ci		err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,
37462306a36Sopenharmony_ci						    hwsfops->type,
37562306a36Sopenharmony_ci						    hwsdev_list);
37662306a36Sopenharmony_ci		break;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci	if (err)
37962306a36Sopenharmony_ci		return err;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	return count;
38262306a36Sopenharmony_ci}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci#define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE)			\
38562306a36Sopenharmony_ci	{							\
38662306a36Sopenharmony_ci		.fops = {					\
38762306a36Sopenharmony_ci			.open = simple_open,			\
38862306a36Sopenharmony_ci			.write = nsim_dev_hwstats_do_write,	\
38962306a36Sopenharmony_ci			.llseek = generic_file_llseek,		\
39062306a36Sopenharmony_ci			.owner = THIS_MODULE,			\
39162306a36Sopenharmony_ci		},						\
39262306a36Sopenharmony_ci		.action = ACTION,				\
39362306a36Sopenharmony_ci		.type = TYPE,					\
39462306a36Sopenharmony_ci	}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cistatic const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops =
39762306a36Sopenharmony_ci	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE,
39862306a36Sopenharmony_ci			      NETDEV_OFFLOAD_XSTATS_TYPE_L3);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_cistatic const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops =
40162306a36Sopenharmony_ci	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE,
40262306a36Sopenharmony_ci			      NETDEV_OFFLOAD_XSTATS_TYPE_L3);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops =
40562306a36Sopenharmony_ci	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL,
40662306a36Sopenharmony_ci			      NETDEV_OFFLOAD_XSTATS_TYPE_L3);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci#undef NSIM_DEV_HWSTATS_FOPS
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ciint nsim_dev_hwstats_init(struct nsim_dev *nsim_dev)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
41362306a36Sopenharmony_ci	struct net *net = nsim_dev_net(nsim_dev);
41462306a36Sopenharmony_ci	int err;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	mutex_init(&hwstats->hwsdev_list_lock);
41762306a36Sopenharmony_ci	INIT_LIST_HEAD(&hwstats->l3_list);
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;
42062306a36Sopenharmony_ci	err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);
42162306a36Sopenharmony_ci	if (err)
42262306a36Sopenharmony_ci		goto err_mutex_destroy;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);
42562306a36Sopenharmony_ci	if (IS_ERR(hwstats->ddir)) {
42662306a36Sopenharmony_ci		err = PTR_ERR(hwstats->ddir);
42762306a36Sopenharmony_ci		goto err_unregister_notifier;
42862306a36Sopenharmony_ci	}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);
43162306a36Sopenharmony_ci	if (IS_ERR(hwstats->l3_ddir)) {
43262306a36Sopenharmony_ci		err = PTR_ERR(hwstats->l3_ddir);
43362306a36Sopenharmony_ci		goto err_remove_hwstats_recursive;
43462306a36Sopenharmony_ci	}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	debugfs_create_file("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,
43762306a36Sopenharmony_ci			    &nsim_dev_hwstats_l3_enable_fops.fops);
43862306a36Sopenharmony_ci	debugfs_create_file("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,
43962306a36Sopenharmony_ci			    &nsim_dev_hwstats_l3_disable_fops.fops);
44062306a36Sopenharmony_ci	debugfs_create_file("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,
44162306a36Sopenharmony_ci			    &nsim_dev_hwstats_l3_fail_fops.fops);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	INIT_DELAYED_WORK(&hwstats->traffic_dw,
44462306a36Sopenharmony_ci			  &nsim_dev_hwstats_traffic_work);
44562306a36Sopenharmony_ci	schedule_delayed_work(&hwstats->traffic_dw,
44662306a36Sopenharmony_ci			      msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
44762306a36Sopenharmony_ci	return 0;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cierr_remove_hwstats_recursive:
45062306a36Sopenharmony_ci	debugfs_remove_recursive(hwstats->ddir);
45162306a36Sopenharmony_cierr_unregister_notifier:
45262306a36Sopenharmony_ci	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
45362306a36Sopenharmony_cierr_mutex_destroy:
45462306a36Sopenharmony_ci	mutex_destroy(&hwstats->hwsdev_list_lock);
45562306a36Sopenharmony_ci	return err;
45662306a36Sopenharmony_ci}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_cistatic void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats,
45962306a36Sopenharmony_ci				      enum netdev_offload_xstats_type type)
46062306a36Sopenharmony_ci{
46162306a36Sopenharmony_ci	struct nsim_dev_hwstats_netdev *hwsdev, *tmp;
46262306a36Sopenharmony_ci	struct list_head *hwsdev_list;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
46562306a36Sopenharmony_ci	if (WARN_ON(!hwsdev_list))
46662306a36Sopenharmony_ci		return;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	mutex_lock(&hwstats->hwsdev_list_lock);
46962306a36Sopenharmony_ci	list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) {
47062306a36Sopenharmony_ci		list_del(&hwsdev->list);
47162306a36Sopenharmony_ci		nsim_dev_hwsdev_fini(hwsdev);
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci	mutex_unlock(&hwstats->hwsdev_list_lock);
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_civoid nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev)
47762306a36Sopenharmony_ci{
47862306a36Sopenharmony_ci	struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
47962306a36Sopenharmony_ci	struct net *net = nsim_dev_net(nsim_dev);
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	cancel_delayed_work_sync(&hwstats->traffic_dw);
48262306a36Sopenharmony_ci	debugfs_remove_recursive(hwstats->ddir);
48362306a36Sopenharmony_ci	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
48462306a36Sopenharmony_ci	nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
48562306a36Sopenharmony_ci	mutex_destroy(&hwstats->hwsdev_list_lock);
48662306a36Sopenharmony_ci}
487