Thomas Bogendoerfer 061df1
From: Jakub Kicinski <kuba@kernel.org>
Thomas Bogendoerfer 061df1
Date: Sat, 30 Oct 2021 10:18:51 -0700
Thomas Bogendoerfer 061df1
Subject: ethtool: don't drop the rtnl_lock half way thru the ioctl
Thomas Bogendoerfer 061df1
Patch-mainline: v5.16-rc1
Thomas Bogendoerfer 061df1
Git-commit: 1af0a0948e28d83bcfa9d48cd0f992f616c5d62e
Thomas Bogendoerfer 061df1
References: jsc#PED-1495
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
devlink compat code needs to drop rtnl_lock to take
Thomas Bogendoerfer 061df1
devlink->lock to ensure correct lock ordering.
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
This is problematic because we're not strictly guaranteed
Thomas Bogendoerfer 061df1
that the netdev will not disappear after we re-lock.
Thomas Bogendoerfer 061df1
It may open a possibility of nested ->begin / ->complete
Thomas Bogendoerfer 061df1
calls.
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
Instead of calling into devlink under rtnl_lock take
Thomas Bogendoerfer 061df1
a ref on the devlink instance and make the call after
Thomas Bogendoerfer 061df1
we've dropped rtnl_lock.
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
We (continue to) assume that netdevs have an implicit
Thomas Bogendoerfer 061df1
reference on the devlink returned from ndo_get_devlink_port
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
Note that ndo_get_devlink_port will now get called
Thomas Bogendoerfer 061df1
under rtnl_lock. That should be fine since none of
Thomas Bogendoerfer 061df1
the drivers seem to be taking serious locks inside
Thomas Bogendoerfer 061df1
ndo_get_devlink_port.
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Thomas Bogendoerfer 061df1
Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
Thomas Bogendoerfer 061df1
Signed-off-by: David S. Miller <davem@davemloft.net>
Thomas Bogendoerfer 061df1
Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
Thomas Bogendoerfer 061df1
---
Thomas Bogendoerfer 061df1
 include/net/devlink.h |    8 ++++----
Thomas Bogendoerfer 061df1
 net/core/devlink.c    |   45 +++++++--------------------------------------
Thomas Bogendoerfer 061df1
 net/ethtool/ioctl.c   |   36 ++++++++++++++++++++++++++++++++----
Thomas Bogendoerfer 061df1
 3 files changed, 43 insertions(+), 46 deletions(-)
Thomas Bogendoerfer 061df1
Thomas Bogendoerfer 061df1
--- a/include/net/devlink.h
Thomas Bogendoerfer 061df1
+++ b/include/net/devlink.h
Thomas Bogendoerfer 061df1
@@ -1729,9 +1729,9 @@ devlink_trap_policers_unregister(struct
Thomas Bogendoerfer 061df1
 struct devlink *__must_check devlink_try_get(struct devlink *devlink);
Thomas Bogendoerfer 061df1
 void devlink_put(struct devlink *devlink);
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
-void devlink_compat_running_version(struct net_device *dev,
Thomas Bogendoerfer 061df1
+void devlink_compat_running_version(struct devlink *devlink,
Thomas Bogendoerfer 061df1
 				    char *buf, size_t len);
Thomas Bogendoerfer 061df1
-int devlink_compat_flash_update(struct net_device *dev, const char *file_name);
Thomas Bogendoerfer 061df1
+int devlink_compat_flash_update(struct devlink *devlink, const char *file_name);
Thomas Bogendoerfer 061df1
 int devlink_compat_phys_port_name_get(struct net_device *dev,
Thomas Bogendoerfer 061df1
 				      char *name, size_t len);
Thomas Bogendoerfer 061df1
 int devlink_compat_switch_id_get(struct net_device *dev,
Thomas Bogendoerfer 061df1
@@ -1749,12 +1749,12 @@ static inline void devlink_put(struct de
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 static inline void
Thomas Bogendoerfer 061df1
-devlink_compat_running_version(struct net_device *dev, char *buf, size_t len)
Thomas Bogendoerfer 061df1
+devlink_compat_running_version(struct devlink *devlink, char *buf, size_t len)
Thomas Bogendoerfer 061df1
 {
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 static inline int
Thomas Bogendoerfer 061df1
-devlink_compat_flash_update(struct net_device *dev, const char *file_name)
Thomas Bogendoerfer 061df1
+devlink_compat_flash_update(struct devlink *devlink, const char *file_name)
Thomas Bogendoerfer 061df1
 {
Thomas Bogendoerfer 061df1
 	return -EOPNOTSUPP;
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
--- a/net/core/devlink.c
Thomas Bogendoerfer 061df1
+++ b/net/core/devlink.c
Thomas Bogendoerfer 061df1
@@ -11283,55 +11283,28 @@ static struct devlink_port *netdev_to_de
Thomas Bogendoerfer 061df1
 	return dev->netdev_ops->ndo_get_devlink_port(dev);
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
-static struct devlink *netdev_to_devlink(struct net_device *dev)
Thomas Bogendoerfer 061df1
-{
Thomas Bogendoerfer 061df1
-	struct devlink_port *devlink_port = netdev_to_devlink_port(dev);
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-	if (!devlink_port)
Thomas Bogendoerfer 061df1
-		return NULL;
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-	return devlink_port->devlink;
Thomas Bogendoerfer 061df1
-}
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-void devlink_compat_running_version(struct net_device *dev,
Thomas Bogendoerfer 061df1
+void devlink_compat_running_version(struct devlink *devlink,
Thomas Bogendoerfer 061df1
 				    char *buf, size_t len)
Thomas Bogendoerfer 061df1
 {
Thomas Bogendoerfer 061df1
-	struct devlink *devlink;
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-	dev_hold(dev);
Thomas Bogendoerfer 061df1
-	rtnl_unlock();
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-	devlink = netdev_to_devlink(dev);
Thomas Bogendoerfer 061df1
-	if (!devlink || !devlink->ops->info_get)
Thomas Bogendoerfer 061df1
-		goto out;
Thomas Bogendoerfer 061df1
+	if (!devlink->ops->info_get)
Thomas Bogendoerfer 061df1
+		return;
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	mutex_lock(&devlink->lock);
Thomas Bogendoerfer 061df1
 	__devlink_compat_running_version(devlink, buf, len);
Thomas Bogendoerfer 061df1
 	mutex_unlock(&devlink->lock);
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-out:
Thomas Bogendoerfer 061df1
-	rtnl_lock();
Thomas Bogendoerfer 061df1
-	dev_put(dev);
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
-int devlink_compat_flash_update(struct net_device *dev, const char *file_name)
Thomas Bogendoerfer 061df1
+int devlink_compat_flash_update(struct devlink *devlink, const char *file_name)
Thomas Bogendoerfer 061df1
 {
Thomas Bogendoerfer 061df1
 	struct devlink_flash_update_params params = {};
Thomas Bogendoerfer 061df1
-	struct devlink *devlink;
Thomas Bogendoerfer 061df1
 	int ret;
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
-	dev_hold(dev);
Thomas Bogendoerfer 061df1
-	rtnl_unlock();
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
-	devlink = netdev_to_devlink(dev);
Thomas Bogendoerfer 061df1
-	if (!devlink || !devlink->ops->flash_update) {
Thomas Bogendoerfer 061df1
-		ret = -EOPNOTSUPP;
Thomas Bogendoerfer 061df1
-		goto out;
Thomas Bogendoerfer 061df1
-	}
Thomas Bogendoerfer 061df1
+	if (!devlink->ops->flash_update)
Thomas Bogendoerfer 061df1
+		return -EOPNOTSUPP;
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	ret = request_firmware(&params.fw, file_name, devlink->dev);
Thomas Bogendoerfer 061df1
 	if (ret)
Thomas Bogendoerfer 061df1
-		goto out;
Thomas Bogendoerfer 061df1
+		return ret;
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	mutex_lock(&devlink->lock);
Thomas Bogendoerfer 061df1
 	devlink_flash_update_begin_notify(devlink);
Thomas Bogendoerfer 061df1
@@ -11341,10 +11314,6 @@ int devlink_compat_flash_update(struct n
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	release_firmware(params.fw);
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
-out:
Thomas Bogendoerfer 061df1
-	rtnl_lock();
Thomas Bogendoerfer 061df1
-	dev_put(dev);
Thomas Bogendoerfer 061df1
-
Thomas Bogendoerfer 061df1
 	return ret;
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
--- a/net/ethtool/ioctl.c
Thomas Bogendoerfer 061df1
+++ b/net/ethtool/ioctl.c
Thomas Bogendoerfer 061df1
@@ -34,12 +34,27 @@
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 /* State held across locks and calls for commands which have devlink fallback */
Thomas Bogendoerfer 061df1
 struct ethtool_devlink_compat {
Thomas Bogendoerfer 061df1
+	struct devlink *devlink;
Thomas Bogendoerfer 061df1
 	union {
Thomas Bogendoerfer 061df1
 		struct ethtool_flash efl;
Thomas Bogendoerfer 061df1
 		struct ethtool_drvinfo info;
Thomas Bogendoerfer 061df1
 	};
Thomas Bogendoerfer 061df1
 };
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
+static struct devlink *netdev_to_devlink_get(struct net_device *dev)
Thomas Bogendoerfer 061df1
+{
Thomas Bogendoerfer 061df1
+	struct devlink_port *devlink_port;
Thomas Bogendoerfer 061df1
+
Thomas Bogendoerfer 061df1
+	if (!dev->netdev_ops->ndo_get_devlink_port)
Thomas Bogendoerfer 061df1
+		return NULL;
Thomas Bogendoerfer 061df1
+
Thomas Bogendoerfer 061df1
+	devlink_port = dev->netdev_ops->ndo_get_devlink_port(dev);
Thomas Bogendoerfer 061df1
+	if (!devlink_port)
Thomas Bogendoerfer 061df1
+		return NULL;
Thomas Bogendoerfer 061df1
+
Thomas Bogendoerfer 061df1
+	return devlink_try_get(devlink_port->devlink);
Thomas Bogendoerfer 061df1
+}
Thomas Bogendoerfer 061df1
+
Thomas Bogendoerfer 061df1
 /*
Thomas Bogendoerfer 061df1
  * Some useful ethtool_ops methods that're device independent.
Thomas Bogendoerfer 061df1
  * If we find that all drivers want to do the same thing here,
Thomas Bogendoerfer 061df1
@@ -752,8 +767,8 @@ ethtool_get_drvinfo(struct net_device *d
Thomas Bogendoerfer 061df1
 		rsp->info.eedump_len = ops->get_eeprom_len(dev);
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	if (!rsp->info.fw_version[0])
Thomas Bogendoerfer 061df1
-		devlink_compat_running_version(dev, rsp->info.fw_version,
Thomas Bogendoerfer 061df1
-					       sizeof(rsp->info.fw_version));
Thomas Bogendoerfer 061df1
+		rsp->devlink = netdev_to_devlink_get(dev);
Thomas Bogendoerfer 061df1
+
Thomas Bogendoerfer 061df1
 	return 0;
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
@@ -2185,8 +2200,10 @@ static int ethtool_set_value(struct net_
Thomas Bogendoerfer 061df1
 static int
Thomas Bogendoerfer 061df1
 ethtool_flash_device(struct net_device *dev, struct ethtool_devlink_compat *req)
Thomas Bogendoerfer 061df1
 {
Thomas Bogendoerfer 061df1
-	if (!dev->ethtool_ops->flash_device)
Thomas Bogendoerfer 061df1
-		return devlink_compat_flash_update(dev, req->efl.data);
Thomas Bogendoerfer 061df1
+	if (!dev->ethtool_ops->flash_device) {
Thomas Bogendoerfer 061df1
+		req->devlink = netdev_to_devlink_get(dev);
Thomas Bogendoerfer 061df1
+		return 0;
Thomas Bogendoerfer 061df1
+	}
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	return dev->ethtool_ops->flash_device(dev, &req->efl);
Thomas Bogendoerfer 061df1
 }
Thomas Bogendoerfer 061df1
@@ -3028,7 +3045,16 @@ int dev_ethtool(struct net *net, struct
Thomas Bogendoerfer 061df1
 		goto exit_free;
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 	switch (ethcmd) {
Thomas Bogendoerfer 061df1
+	case ETHTOOL_FLASHDEV:
Thomas Bogendoerfer 061df1
+		if (state->devlink)
Thomas Bogendoerfer 061df1
+			rc = devlink_compat_flash_update(state->devlink,
Thomas Bogendoerfer 061df1
+							 state->efl.data);
Thomas Bogendoerfer 061df1
+		break;
Thomas Bogendoerfer 061df1
 	case ETHTOOL_GDRVINFO:
Thomas Bogendoerfer 061df1
+		if (state->devlink)
Thomas Bogendoerfer 061df1
+			devlink_compat_running_version(state->devlink,
Thomas Bogendoerfer 061df1
+						       state->info.fw_version,
Thomas Bogendoerfer 061df1
+						       sizeof(state->info.fw_version));
Thomas Bogendoerfer 061df1
 		if (copy_to_user(useraddr, &state->info, sizeof(state->info))) {
Thomas Bogendoerfer 061df1
 			rc = -EFAULT;
Thomas Bogendoerfer 061df1
 			goto exit_free;
Thomas Bogendoerfer 061df1
@@ -3037,6 +3063,8 @@ int dev_ethtool(struct net *net, struct
Thomas Bogendoerfer 061df1
 	}
Thomas Bogendoerfer 061df1
 
Thomas Bogendoerfer 061df1
 exit_free:
Thomas Bogendoerfer 061df1
+	if (state->devlink)
Thomas Bogendoerfer 061df1
+		devlink_put(state->devlink);
Thomas Bogendoerfer 061df1
 	kfree(state);
Thomas Bogendoerfer 061df1
 	return rc;
Thomas Bogendoerfer 061df1
 }