Commit 2dbf4c2e authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'ethtool-runtime-pm'



Heiner Kallweit says:

====================
ethtool: runtime-resume netdev parent before ethtool ops

If a network device is runtime-suspended then:
- network device may be flagged as detached and all ethtool ops (even if
  not accessing the device) will fail because netif_device_present()
  returns false
- ethtool ops may fail because device is not accessible (e.g. because being
  in D3 in case of a PCI device)

It may not be desirable that userspace can't use even simple ethtool ops
that not access the device if interface or link is down. To be more friendly
to userspace let's ensure that device is runtime-resumed when executing
ethtool ops in kernel.

This patch series covers the typical case that the netdev parent is power-
managed, e.g. a PCI device. Not sure whether cases exist where the netdev
itself is power-managed. If yes then we may need an extension for this.
But the series as-is at least shouldn't cause problems in that case.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents c32325b8 d43c65b0
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/rtnetlink.h>
#include <linux/sched/signal.h>
#include <linux/net.h>
#include <linux/pm_runtime.h>
#include <net/devlink.h>
#include <net/xdp_sock_drv.h>
#include <net/flow_offload.h>
@@ -2692,7 +2693,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
	int rc;
	netdev_features_t old_features;

	if (!dev || !netif_device_present(dev))
	if (!dev)
		return -ENODEV;

	if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
@@ -2748,10 +2749,18 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
			return -EPERM;
	}

	if (dev->dev.parent)
		pm_runtime_get_sync(dev->dev.parent);

	if (!netif_device_present(dev)) {
		rc = -ENODEV;
		goto out;
	}

	if (dev->ethtool_ops->begin) {
		rc = dev->ethtool_ops->begin(dev);
		if (rc < 0)
			return rc;
			goto out;
	}
	old_features = dev->features;

@@ -2970,6 +2979,9 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)

	if (old_features != dev->features)
		netdev_features_change(dev);
out:
	if (dev->dev.parent)
		pm_runtime_put(dev->dev.parent);

	return rc;
}
+39 −6
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include <linux/pm_runtime.h>
#include "netlink.h"

static struct genl_family ethtool_genl_family;
@@ -29,6 +30,44 @@ const struct nla_policy ethnl_header_policy_stats[] = {
							  ETHTOOL_FLAGS_STATS),
};

int ethnl_ops_begin(struct net_device *dev)
{
	int ret;

	if (!dev)
		return 0;

	if (dev->dev.parent)
		pm_runtime_get_sync(dev->dev.parent);

	if (!netif_device_present(dev)) {
		ret = -ENODEV;
		goto err;
	}

	if (dev->ethtool_ops->begin) {
		ret = dev->ethtool_ops->begin(dev);
		if (ret)
			goto err;
	}

	return 0;
err:
	if (dev->dev.parent)
		pm_runtime_put(dev->dev.parent);

	return ret;
}

void ethnl_ops_complete(struct net_device *dev)
{
	if (dev && dev->ethtool_ops->complete)
		dev->ethtool_ops->complete(dev);

	if (dev->dev.parent)
		pm_runtime_put(dev->dev.parent);
}

/**
 * ethnl_parse_header_dev_get() - parse request header
 * @req_info:    structure to put results into
@@ -101,12 +140,6 @@ int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
		return -EINVAL;
	}

	if (dev && !netif_device_present(dev)) {
		dev_put(dev);
		NL_SET_ERR_MSG(extack, "device not present");
		return -ENODEV;
	}

	req_info->dev = dev;
	req_info->flags = flags;
	return 0;
+2 −13
Original line number Diff line number Diff line
@@ -247,19 +247,8 @@ struct ethnl_reply_data {
	struct net_device		*dev;
};

static inline int ethnl_ops_begin(struct net_device *dev)
{
	if (dev && dev->ethtool_ops->begin)
		return dev->ethtool_ops->begin(dev);
	else
		return 0;
}

static inline void ethnl_ops_complete(struct net_device *dev)
{
	if (dev && dev->ethtool_ops->complete)
		dev->ethtool_ops->complete(dev);
}
int ethnl_ops_begin(struct net_device *dev);
void ethnl_ops_complete(struct net_device *dev);

/**
 * struct ethnl_request_ops - unified handling of GET requests