summaryrefslogtreecommitdiffstats
path: root/net/core/ethtool.c
diff options
context:
space:
mode:
authorBen Hutchings <bhutchings@solarflare.com>2011-04-02 01:35:15 +0200
committerBen Hutchings <bhutchings@solarflare.com>2011-04-05 16:12:01 +0200
commit68f512f21a64c9b264df6c61a9333e7890faf74b (patch)
tree4635193aaec903f09ae69df4dc880296d74c5135 /net/core/ethtool.c
parentethtool: Fill out and update comment for struct ethtool_ops (diff)
downloadlinux-68f512f21a64c9b264df6c61a9333e7890faf74b.tar.xz
linux-68f512f21a64c9b264df6c61a9333e7890faf74b.zip
ethtool: Change ETHTOOL_PHYS_ID implementation to allow dropping RTNL
The ethtool ETHTOOL_PHYS_ID command runs for an arbitrarily long period of time, holding the RTNL lock. This blocks routing updates, device enumeration, and various important operations that one might want to keep running while hunting for the flashing LED. We need to drop the RTNL lock during this operation, but currently the core implementation is a thin wrapper around a driver operation and drivers may well depend upon holding the lock. Define a new driver operation 'set_phys_id' with an argument that sets the ID indicator on/off/inactive/active (the last optional, for any driver or firmware that prefers to handle blinking asynchronously). When this is defined, the ethtool core drops the lock while waiting and only acquires it around calls to this operation. Deprecate the 'phys_id' operation in favour of this. It can be removed once all in-tree drivers are converted. Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
Diffstat (limited to '')
-rw-r--r--net/core/ethtool.c55
1 files changed, 53 insertions, 2 deletions
diff --git a/net/core/ethtool.c b/net/core/ethtool.c
index 74ead9eca126..1c95f0fb8b3a 100644
--- a/net/core/ethtool.c
+++ b/net/core/ethtool.c
@@ -21,6 +21,8 @@
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
+#include <linux/rtnetlink.h>
+#include <linux/sched.h>
/*
* Some useful ethtool_ops methods that're device independent.
@@ -1618,14 +1620,63 @@ out:
static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
{
struct ethtool_value id;
+ static bool busy;
+ int rc;
- if (!dev->ethtool_ops->phys_id)
+ if (!dev->ethtool_ops->set_phys_id && !dev->ethtool_ops->phys_id)
return -EOPNOTSUPP;
+ if (busy)
+ return -EBUSY;
+
if (copy_from_user(&id, useraddr, sizeof(id)))
return -EFAULT;
- return dev->ethtool_ops->phys_id(dev, id.data);
+ if (!dev->ethtool_ops->set_phys_id)
+ /* Do it the old way */
+ return dev->ethtool_ops->phys_id(dev, id.data);
+
+ rc = dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE);
+ if (rc && rc != -EINVAL)
+ return rc;
+
+ /* Drop the RTNL lock while waiting, but prevent reentry or
+ * removal of the device.
+ */
+ busy = true;
+ dev_hold(dev);
+ rtnl_unlock();
+
+ if (rc == 0) {
+ /* Driver will handle this itself */
+ schedule_timeout_interruptible(
+ id.data ? id.data : MAX_SCHEDULE_TIMEOUT);
+ } else {
+ /* Driver expects to be called periodically */
+ do {
+ rtnl_lock();
+ rc = dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_ON);
+ rtnl_unlock();
+ if (rc)
+ break;
+ schedule_timeout_interruptible(HZ / 2);
+
+ rtnl_lock();
+ rc = dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_OFF);
+ rtnl_unlock();
+ if (rc)
+ break;
+ schedule_timeout_interruptible(HZ / 2);
+ } while (!signal_pending(current) &&
+ (id.data == 0 || --id.data != 0));
+ }
+
+ rtnl_lock();
+ dev_put(dev);
+ busy = false;
+
+ (void)dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE);
+ return rc;
}
static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)