480 lines · 12.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0 #include <linux/debugfs.h> #include "netdevsim.h" #define NSIM_DEV_HWSTATS_TRAFFIC_MS	100 static struct list_head *nsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats *hwstats,			       enum netdev_offload_xstats_type type){	switch (type) {	case NETDEV_OFFLOAD_XSTATS_TYPE_L3:		return &hwstats->l3_list;	} 	WARN_ON_ONCE(1);	return NULL;} static void nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats *hwstats,					  enum netdev_offload_xstats_type type){	struct nsim_dev_hwstats_netdev *hwsdev;	struct list_head *hwsdev_list; 	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);	if (WARN_ON(!hwsdev_list))		return; 	list_for_each_entry(hwsdev, hwsdev_list, list) {		if (hwsdev->enabled) {			hwsdev->stats.rx_packets += 1;			hwsdev->stats.tx_packets += 2;			hwsdev->stats.rx_bytes += 100;			hwsdev->stats.tx_bytes += 300;		}	}} static void nsim_dev_hwstats_traffic_work(struct work_struct *work){	struct nsim_dev_hwstats *hwstats; 	hwstats = container_of(work, struct nsim_dev_hwstats, traffic_dw.work);	mutex_lock(&hwstats->hwsdev_list_lock);	nsim_dev_hwstats_traffic_bump(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);	mutex_unlock(&hwstats->hwsdev_list_lock); 	schedule_delayed_work(&hwstats->traffic_dw,			      msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));} static struct nsim_dev_hwstats_netdev *nsim_dev_hwslist_find_hwsdev(struct list_head *hwsdev_list,			     int ifindex){	struct nsim_dev_hwstats_netdev *hwsdev; 	list_for_each_entry(hwsdev, hwsdev_list, list) {		if (hwsdev->netdev->ifindex == ifindex)			return hwsdev;	} 	return NULL;} static int nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev *hwsdev,				  struct netlink_ext_ack *extack){	if (hwsdev->fail_enable) {		hwsdev->fail_enable = false;		NL_SET_ERR_MSG_MOD(extack, "Stats enablement set to fail");		return -ECANCELED;	} 	hwsdev->enabled = true;	return 0;} static void nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev *hwsdev){	hwsdev->enabled = false;	memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));} static intnsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev *hwsdev,			     struct netdev_notifier_offload_xstats_info *info){	netdev_offload_xstats_report_delta(info->report_delta, &hwsdev->stats);	memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));	return 0;} static voidnsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev *hwsdev,			    struct netdev_notifier_offload_xstats_info *info){	if (hwsdev->enabled)		netdev_offload_xstats_report_used(info->report_used);} static int nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats *hwstats,					     struct net_device *dev,					     unsigned long event, void *ptr){	struct netdev_notifier_offload_xstats_info *info;	struct nsim_dev_hwstats_netdev *hwsdev;	struct list_head *hwsdev_list;	int err = 0; 	info = ptr;	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, info->type);	if (!hwsdev_list)		return 0; 	mutex_lock(&hwstats->hwsdev_list_lock); 	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);	if (!hwsdev)		goto out; 	switch (event) {	case NETDEV_OFFLOAD_XSTATS_ENABLE:		err = nsim_dev_hwsdev_enable(hwsdev, info->info.extack);		break;	case NETDEV_OFFLOAD_XSTATS_DISABLE:		nsim_dev_hwsdev_disable(hwsdev);		break;	case NETDEV_OFFLOAD_XSTATS_REPORT_USED:		nsim_dev_hwsdev_report_used(hwsdev, info);		break;	case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:		err = nsim_dev_hwsdev_report_delta(hwsdev, info);		break;	} out:	mutex_unlock(&hwstats->hwsdev_list_lock);	return err;} static void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev){	dev_put(hwsdev->netdev);	kfree(hwsdev);} static void__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,				    struct net_device *dev,				    enum netdev_offload_xstats_type type){	struct nsim_dev_hwstats_netdev *hwsdev;	struct list_head *hwsdev_list; 	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);	if (WARN_ON(!hwsdev_list))		return; 	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);	if (!hwsdev)		return; 	list_del(&hwsdev->list);	nsim_dev_hwsdev_fini(hwsdev);} static void nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,					      struct net_device *dev){	mutex_lock(&hwstats->hwsdev_list_lock);	__nsim_dev_hwstats_event_unregister(hwstats, dev,					    NETDEV_OFFLOAD_XSTATS_TYPE_L3);	mutex_unlock(&hwstats->hwsdev_list_lock);} static int nsim_dev_hwstats_event(struct nsim_dev_hwstats *hwstats,				  struct net_device *dev,				  unsigned long event, void *ptr){	switch (event) {	case NETDEV_OFFLOAD_XSTATS_ENABLE:	case NETDEV_OFFLOAD_XSTATS_DISABLE:	case NETDEV_OFFLOAD_XSTATS_REPORT_USED:	case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:		return nsim_dev_hwstats_event_off_xstats(hwstats, dev,							 event, ptr);	case NETDEV_UNREGISTER:		nsim_dev_hwstats_event_unregister(hwstats, dev);		break;	} 	return 0;} static int nsim_dev_netdevice_event(struct notifier_block *nb,				    unsigned long event, void *ptr){	struct net_device *dev = netdev_notifier_info_to_dev(ptr);	struct nsim_dev_hwstats *hwstats;	int err = 0; 	hwstats = container_of(nb, struct nsim_dev_hwstats, netdevice_nb);	err = nsim_dev_hwstats_event(hwstats, dev, event, ptr);	if (err)		return notifier_from_errno(err); 	return NOTIFY_OK;} static intnsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats *hwstats,				int ifindex,				enum netdev_offload_xstats_type type,				struct list_head *hwsdev_list){	struct nsim_dev_hwstats_netdev *hwsdev;	struct nsim_dev *nsim_dev;	struct net_device *netdev;	struct net *net;	int err = 0; 	nsim_dev = container_of(hwstats, struct nsim_dev, hwstats);	net = nsim_dev_net(nsim_dev); 	rtnl_lock();	mutex_lock(&hwstats->hwsdev_list_lock);	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);	if (hwsdev)		goto out_unlock_list; 	netdev = dev_get_by_index(net, ifindex);	if (!netdev) {		err = -ENODEV;		goto out_unlock_list;	} 	hwsdev = kzalloc_obj(*hwsdev);	if (!hwsdev) {		err = -ENOMEM;		goto out_put_netdev;	} 	hwsdev->netdev = netdev;	list_add_tail(&hwsdev->list, hwsdev_list);	mutex_unlock(&hwstats->hwsdev_list_lock); 	if (netdev_offload_xstats_enabled(netdev, type)) {		nsim_dev_hwsdev_enable(hwsdev, NULL);		rtnl_offload_xstats_notify(netdev);	} 	rtnl_unlock();	return err; out_put_netdev:	dev_put(netdev);out_unlock_list:	mutex_unlock(&hwstats->hwsdev_list_lock);	rtnl_unlock();	return err;} static intnsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats,				 int ifindex,				 enum netdev_offload_xstats_type type,				 struct list_head *hwsdev_list){	struct nsim_dev_hwstats_netdev *hwsdev;	int err = 0; 	rtnl_lock();	mutex_lock(&hwstats->hwsdev_list_lock);	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);	if (hwsdev)		list_del(&hwsdev->list);	mutex_unlock(&hwstats->hwsdev_list_lock); 	if (!hwsdev) {		err = -ENOENT;		goto unlock_out;	} 	if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) {		netdev_offload_xstats_push_delta(hwsdev->netdev, type,						 &hwsdev->stats);		rtnl_offload_xstats_notify(hwsdev->netdev);	}	nsim_dev_hwsdev_fini(hwsdev); unlock_out:	rtnl_unlock();	return err;} static intnsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats,			      int ifindex,			      enum netdev_offload_xstats_type type,			      struct list_head *hwsdev_list){	struct nsim_dev_hwstats_netdev *hwsdev;	int err = 0; 	mutex_lock(&hwstats->hwsdev_list_lock); 	hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);	if (!hwsdev) {		err = -ENOENT;		goto err_hwsdev_list_unlock;	} 	hwsdev->fail_enable = true; err_hwsdev_list_unlock:	mutex_unlock(&hwstats->hwsdev_list_lock);	return err;} enum nsim_dev_hwstats_do {	NSIM_DEV_HWSTATS_DO_DISABLE,	NSIM_DEV_HWSTATS_DO_ENABLE,	NSIM_DEV_HWSTATS_DO_FAIL,}; struct nsim_dev_hwstats_fops {	enum nsim_dev_hwstats_do action;	enum netdev_offload_xstats_type type;}; static ssize_tnsim_dev_hwstats_do_write(struct file *file,			  const char __user *data,			  size_t count, loff_t *ppos){	struct nsim_dev_hwstats *hwstats = file->private_data;	const struct nsim_dev_hwstats_fops *hwsfops;	struct list_head *hwsdev_list;	int ifindex;	int err; 	hwsfops = debugfs_get_aux(file); 	err = kstrtoint_from_user(data, count, 0, &ifindex);	if (err)		return err; 	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type);	if (WARN_ON(!hwsdev_list))		return -EINVAL; 	switch (hwsfops->action) {	case NSIM_DEV_HWSTATS_DO_DISABLE:		err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex,						       hwsfops->type,						       hwsdev_list);		break;	case NSIM_DEV_HWSTATS_DO_ENABLE:		err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,						      hwsfops->type,						      hwsdev_list);		break;	case NSIM_DEV_HWSTATS_DO_FAIL:		err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,						    hwsfops->type,						    hwsdev_list);		break;	}	if (err)		return err; 	return count;} static struct debugfs_short_fops debugfs_ops = {	.write = nsim_dev_hwstats_do_write,	.llseek = generic_file_llseek,}; #define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE)			\	{							\		.action = ACTION,				\		.type = TYPE,					\	} static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops =	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE,			      NETDEV_OFFLOAD_XSTATS_TYPE_L3); static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops =	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE,			      NETDEV_OFFLOAD_XSTATS_TYPE_L3); static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops =	NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL,			      NETDEV_OFFLOAD_XSTATS_TYPE_L3); #undef NSIM_DEV_HWSTATS_FOPS int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev){	struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;	struct net *net = nsim_dev_net(nsim_dev);	int err; 	mutex_init(&hwstats->hwsdev_list_lock);	INIT_LIST_HEAD(&hwstats->l3_list); 	hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;	err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);	if (err)		goto err_mutex_destroy; 	hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);	if (IS_ERR(hwstats->ddir)) {		err = PTR_ERR(hwstats->ddir);		goto err_unregister_notifier;	} 	hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);	if (IS_ERR(hwstats->l3_ddir)) {		err = PTR_ERR(hwstats->l3_ddir);		goto err_remove_hwstats_recursive;	} 	debugfs_create_file_aux("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,			    &nsim_dev_hwstats_l3_enable_fops, &debugfs_ops);	debugfs_create_file_aux("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,			    &nsim_dev_hwstats_l3_disable_fops, &debugfs_ops);	debugfs_create_file_aux("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,			    &nsim_dev_hwstats_l3_fail_fops, &debugfs_ops); 	INIT_DELAYED_WORK(&hwstats->traffic_dw,			  &nsim_dev_hwstats_traffic_work);	schedule_delayed_work(&hwstats->traffic_dw,			      msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));	return 0; err_remove_hwstats_recursive:	debugfs_remove_recursive(hwstats->ddir);err_unregister_notifier:	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);err_mutex_destroy:	mutex_destroy(&hwstats->hwsdev_list_lock);	return err;} static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats,				      enum netdev_offload_xstats_type type){	struct nsim_dev_hwstats_netdev *hwsdev, *tmp;	struct list_head *hwsdev_list; 	hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);	if (WARN_ON(!hwsdev_list))		return; 	mutex_lock(&hwstats->hwsdev_list_lock);	list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) {		list_del(&hwsdev->list);		nsim_dev_hwsdev_fini(hwsdev);	}	mutex_unlock(&hwstats->hwsdev_list_lock);} void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev){	struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;	struct net *net = nsim_dev_net(nsim_dev); 	cancel_delayed_work_sync(&hwstats->traffic_dw);	debugfs_remove_recursive(hwstats->ddir);	unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);	nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);	mutex_destroy(&hwstats->hwsdev_list_lock);}