* [PATCH net-next 01/14] net: ethtool: cmis_cdb: hold instance lock for ops locked devices
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-29 11:25 ` Jakub Sitnicki
2026-05-28 23:16 ` [PATCH net-next 02/14] net: ethtool: make sure __ethtool_get_link_ksettings() is ops-locked Jakub Kicinski
` (13 subsequent siblings)
14 siblings, 1 reply; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
FW module flashing was written so that the flashing happens
without holding rtnl_lock. This allows flashing multiple modules
at once. Current drivers can handle that well, but we should
let drivers depend on the netdev instance lock. Instance lock
is per netdev, and so is the module so we won't break parallel
updates.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/netdev_lock.h | 6 ++++++
net/ethtool/cmis_cdb.c | 3 +++
net/ethtool/cmis_fw_update.c | 8 ++------
net/ethtool/module.c | 2 ++
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h
index 3d3aef80beac..f4c77899fb86 100644
--- a/include/net/netdev_lock.h
+++ b/include/net/netdev_lock.h
@@ -80,6 +80,12 @@ netdev_ops_assert_locked_or_invisible(const struct net_device *dev)
netdev_ops_assert_locked(dev);
}
+static inline void netdev_assert_locked_if_ops(const struct net_device *dev)
+{
+ if (netdev_need_ops_lock(dev))
+ netdev_assert_locked(dev);
+}
+
static inline void netdev_lock_ops_compat(struct net_device *dev)
{
if (netdev_need_ops_lock(dev))
diff --git a/net/ethtool/cmis_cdb.c b/net/ethtool/cmis_cdb.c
index f3a53a984460..d2037c0c97f9 100644
--- a/net/ethtool/cmis_cdb.c
+++ b/net/ethtool/cmis_cdb.c
@@ -2,6 +2,7 @@
#include <linux/ethtool.h>
#include <linux/jiffies.h>
+#include <net/netdev_lock.h>
#include "common.h"
#include "module_fw.h"
@@ -179,6 +180,7 @@ cmis_cdb_validate_password(struct ethtool_cmis_cdb *cdb,
pe_pl = *((struct cmis_password_entry_pl *)page_data.data);
pe_pl.password = params->password;
+ netdev_assert_locked_if_ops(dev);
err = ops->set_module_eeprom_by_page(dev, &page_data, &extack);
if (err < 0) {
if (extack._msg)
@@ -546,6 +548,7 @@ __ethtool_cmis_cdb_execute_cmd(struct net_device *dev,
if (!page_data->data)
return -ENOMEM;
+ netdev_assert_locked_if_ops(dev);
err = ops->set_module_eeprom_by_page(dev, page_data, &extack);
if (err < 0) {
if (extack._msg)
diff --git a/net/ethtool/cmis_fw_update.c b/net/ethtool/cmis_fw_update.c
index 291d04d2776a..8142ac93a381 100644
--- a/net/ethtool/cmis_fw_update.c
+++ b/net/ethtool/cmis_fw_update.c
@@ -435,13 +435,9 @@ cmis_fw_update_commit_image(struct ethtool_cmis_cdb *cdb,
static int cmis_fw_update_reset(struct net_device *dev)
{
__u32 reset_data = ETH_RESET_PHY;
- int ret;
- netdev_lock_ops(dev);
- ret = dev->ethtool_ops->reset(dev, &reset_data);
- netdev_unlock_ops(dev);
-
- return ret;
+ netdev_assert_locked_if_ops(dev);
+ return dev->ethtool_ops->reset(dev, &reset_data);
}
void
diff --git a/net/ethtool/module.c b/net/ethtool/module.c
index ea4fb2a76650..c3388e6d7ec8 100644
--- a/net/ethtool/module.c
+++ b/net/ethtool/module.c
@@ -226,7 +226,9 @@ static void module_flash_fw_work(struct work_struct *work)
module_fw = container_of(work, struct ethtool_module_fw_flash, work);
dev = module_fw->fw_update.dev;
+ netdev_lock_ops(dev);
ethtool_cmis_fw_update(&module_fw->fw_update);
+ netdev_unlock_ops(dev);
module_flash_fw_work_list_del(&module_fw->list);
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 01/14] net: ethtool: cmis_cdb: hold instance lock for ops locked devices
2026-05-28 23:16 ` [PATCH net-next 01/14] net: ethtool: cmis_cdb: hold instance lock for ops locked devices Jakub Kicinski
@ 2026-05-29 11:25 ` Jakub Sitnicki
0 siblings, 0 replies; 19+ messages in thread
From: Jakub Sitnicki @ 2026-05-29 11:25 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms,
michael.chan, joshwash, tariqt, haiyangz, linux,
maxime.chevallier, willemb, ernis, sdf.kernel, kory.maincent,
danieller, idosch
On Thu, May 28, 2026 at 04:16 PM -07, Jakub Kicinski wrote:
> FW module flashing was written so that the flashing happens
> without holding rtnl_lock. This allows flashing multiple modules
> at once. Current drivers can handle that well, but we should
> let drivers depend on the netdev instance lock. Instance lock
> is per netdev, and so is the module so we won't break parallel
> updates.
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
> include/net/netdev_lock.h | 6 ++++++
> net/ethtool/cmis_cdb.c | 3 +++
> net/ethtool/cmis_fw_update.c | 8 ++------
> net/ethtool/module.c | 2 ++
> 4 files changed, 13 insertions(+), 6 deletions(-)
>
> diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h
> index 3d3aef80beac..f4c77899fb86 100644
> --- a/include/net/netdev_lock.h
> +++ b/include/net/netdev_lock.h
> @@ -80,6 +80,12 @@ netdev_ops_assert_locked_or_invisible(const struct net_device *dev)
> netdev_ops_assert_locked(dev);
> }
>
> +static inline void netdev_assert_locked_if_ops(const struct net_device *dev)
> +{
> + if (netdev_need_ops_lock(dev))
> + netdev_assert_locked(dev);
> +}
> +
Nit (because in the kernel naming is a competitive sport):
How about we name that netdev_assert_locked_ops() so it's clear that
this checks if netdev_{lock,unlock}_ops() has been called?
I'm guessing the intention behind the name was to make it
distinguishable from the legacy netdev_ops_assert_locked().
My suggestion would be to rename that one to
netdev_assert_locked_ops_compat() following the
netdev_{lock,unlock}_ops_compat() naming scheme. That should avoid
confusion and provide a clear signal which one we're phasing out.
Symmetry is beautiful :-)
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 02/14] net: ethtool: make sure __ethtool_get_link_ksettings() is ops-locked
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 01/14] net: ethtool: cmis_cdb: hold instance lock for ops locked devices Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 03/14] net: ethtool: serialize broadcast notification sequence allocation Jakub Kicinski
` (12 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
__ethtool_get_link_ksettings() is exported and called from sysfs
and many drivers. Looks like commit 2bcf4772e45a ("net: ethtool:
try to protect all callback with netdev instance lock")
missed adding the lock around it. Not treating this as a fix because
I don't think any driver cares at this point, but if we want to
remove the rtnl_lock protection this will become critical.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/ethtool.h | 2 ++
net/ethtool/ioctl.c | 20 +++++++++++++++++---
net/ethtool/linkinfo.c | 4 ++--
net/ethtool/linkmodes.c | 4 ++--
4 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 1cb0740ba331..0caaeb91b094 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -325,6 +325,8 @@ struct ethtool_link_ksettings {
extern int
__ethtool_get_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *link_ksettings);
+int ethtool_get_link_ksettings_locked(struct net_device *dev,
+ struct ethtool_link_ksettings *link_ksettings);
struct ethtool_keee {
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index bd97f9b9bf18..1d74ee67e77a 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -436,10 +436,10 @@ struct ethtool_link_usettings {
};
/* Internal kernel helper to query a device ethtool_link_settings. */
-int __ethtool_get_link_ksettings(struct net_device *dev,
- struct ethtool_link_ksettings *link_ksettings)
+int ethtool_get_link_ksettings_locked(struct net_device *dev,
+ struct ethtool_link_ksettings *link_ksettings)
{
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
if (!dev->ethtool_ops->get_link_ksettings)
return -EOPNOTSUPP;
@@ -450,6 +450,20 @@ int __ethtool_get_link_ksettings(struct net_device *dev,
memset(link_ksettings, 0, sizeof(*link_ksettings));
return dev->ethtool_ops->get_link_ksettings(dev, link_ksettings);
}
+
+/* Convenience helper for callers that hold only rtnl_lock(). */
+int __ethtool_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *link_ksettings)
+{
+ int ret;
+
+ ASSERT_RTNL();
+
+ netdev_lock_ops(dev);
+ ret = ethtool_get_link_ksettings_locked(dev, link_ksettings);
+ netdev_unlock_ops(dev);
+ return ret;
+}
EXPORT_SYMBOL(__ethtool_get_link_ksettings);
/* convert ethtool_link_usettings in user space to a kernel internal
diff --git a/net/ethtool/linkinfo.c b/net/ethtool/linkinfo.c
index 244ff92e2ff9..0cdcf0887b01 100644
--- a/net/ethtool/linkinfo.c
+++ b/net/ethtool/linkinfo.c
@@ -34,7 +34,7 @@ static int linkinfo_prepare_data(const struct ethnl_req_info *req_base,
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
- ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
+ ret = ethtool_get_link_ksettings_locked(dev, &data->ksettings);
if (ret < 0)
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
ethnl_ops_complete(dev);
@@ -104,7 +104,7 @@ ethnl_set_linkinfo(struct ethnl_req_info *req_info, struct genl_info *info)
bool mod = false;
int ret;
- ret = __ethtool_get_link_ksettings(dev, &ksettings);
+ ret = ethtool_get_link_ksettings_locked(dev, &ksettings);
if (ret < 0) {
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
return ret;
diff --git a/net/ethtool/linkmodes.c b/net/ethtool/linkmodes.c
index 30d703531652..e7999aed9200 100644
--- a/net/ethtool/linkmodes.c
+++ b/net/ethtool/linkmodes.c
@@ -39,7 +39,7 @@ static int linkmodes_prepare_data(const struct ethnl_req_info *req_base,
if (ret < 0)
return ret;
- ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
+ ret = ethtool_get_link_ksettings_locked(dev, &data->ksettings);
if (ret < 0) {
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
goto out;
@@ -324,7 +324,7 @@ ethnl_set_linkmodes(struct ethnl_req_info *req_info, struct genl_info *info)
bool mod = false;
int ret;
- ret = __ethtool_get_link_ksettings(dev, &ksettings);
+ ret = ethtool_get_link_ksettings_locked(dev, &ksettings);
if (ret < 0) {
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
return ret;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 03/14] net: ethtool: serialize broadcast notification sequence allocation
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 01/14] net: ethtool: cmis_cdb: hold instance lock for ops locked devices Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 02/14] net: ethtool: make sure __ethtool_get_link_ksettings() is ops-locked Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion Jakub Kicinski
` (11 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
ethnl_bcast_seq is a global counter stamped into the nlmsg_seq field
of every multicast notification, allowing userspace to detect dropped
messages. Today the ordering is achieved by using rtnl_lock()
Moving forward we will want ethtool ops to run under just the netdev
instance lock so to establish ordering we need a separate lock
for notifications. With the netdev instance locks operations on
different devices may bypass each other but the expectation is
that it should not matter. What we need to prevent is:
- notification IDs getting out of order
- operations on one device getting out of order
For simplicity defer allocating the ID of the notification right
before the notification is delivered. This removes the need for
special handling in ethnl_rss_create_send_ntf().
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/ethtool/netlink.h | 1 -
net/ethtool/netlink.c | 28 +++++++++++++++++-----------
| 1 -
3 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 674c9c19529b..f94aaa66379c 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -10,7 +10,6 @@
struct ethnl_req_info;
-u32 ethnl_bcast_seq_next(void);
int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net,
struct netlink_ext_ack *extack,
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6cbd13b61bd1..902ff7d0a71d 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -13,6 +13,12 @@
static struct genl_family ethtool_genl_family;
static bool ethnl_ok __read_mostly;
+
+/* Serializes broadcast notification sequence allocation with the multicast
+ * send, so that userspace observes nlmsg_seq monotonic in receive order
+ * regardless of which lock the caller holds (rtnl or instance lock).
+ */
+static DEFINE_MUTEX(ethnl_bcast_lock);
static u32 ethnl_bcast_seq;
#define ETHTOOL_FLAGS_BASIC (ETHTOOL_FLAG_COMPACT_BITSETS | \
@@ -82,12 +88,6 @@ static void ethnl_sock_priv_destroy(void *priv)
}
}
-u32 ethnl_bcast_seq_next(void)
-{
- ASSERT_RTNL();
- return ++ethnl_bcast_seq;
-}
-
int ethnl_ops_begin(struct net_device *dev)
{
int ret;
@@ -329,8 +329,7 @@ void *ethnl_dump_put(struct sk_buff *skb, struct netlink_callback *cb, u8 cmd)
void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd)
{
- return genlmsg_put(skb, 0, ++ethnl_bcast_seq, ðtool_genl_family, 0,
- cmd);
+ return genlmsg_put(skb, 0, 0, ðtool_genl_family, 0, cmd);
}
void *ethnl_unicast_put(struct sk_buff *skb, u32 portid, u32 seq, u8 cmd)
@@ -340,8 +339,15 @@ void *ethnl_unicast_put(struct sk_buff *skb, u32 portid, u32 seq, u8 cmd)
int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
{
- return genlmsg_multicast_netns(ðtool_genl_family, dev_net(dev), skb,
- 0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
+ struct nlmsghdr *nlh = nlmsg_hdr(skb);
+ int ret;
+
+ mutex_lock(ðnl_bcast_lock);
+ nlh->nlmsg_seq = ++ethnl_bcast_seq;
+ ret = genlmsg_multicast_netns(ðtool_genl_family, dev_net(dev), skb,
+ 0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
+ mutex_unlock(ðnl_bcast_lock);
+ return ret;
}
/* GET request helpers */
@@ -1081,7 +1087,7 @@ void ethnl_notify(struct net_device *dev, unsigned int cmd,
{
if (unlikely(!ethnl_ok))
return;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
if (likely(cmd < ARRAY_SIZE(ethnl_notify_handlers) &&
ethnl_notify_handlers[cmd]))
--git a/net/ethtool/rss.c b/net/ethtool/rss.c
index 53792f53f922..65bad23d5c59 100644
--- a/net/ethtool/rss.c
+++ b/net/ethtool/rss.c
@@ -995,7 +995,6 @@ ethnl_rss_create_send_ntf(const struct sk_buff *rsp, struct net_device *dev)
nlh = nlmsg_hdr(ntf);
/* Convert the reply into a notification */
nlh->nlmsg_pid = 0;
- nlh->nlmsg_seq = ethnl_bcast_seq_next();
genl_hdr = nlmsg_data(nlh);
genl_hdr->cmd = ETHTOOL_MSG_RSS_CREATE_NTF;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (2 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 03/14] net: ethtool: serialize broadcast notification sequence allocation Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-29 8:43 ` Maxime Chevallier
2026-05-28 23:16 ` [PATCH net-next 05/14] net: ethtool: make dev->hwprov ops-protected Jakub Kicinski
` (10 subsequent siblings)
14 siblings, 1 reply; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
phydev <> netdev linking and lifecycle depends on rtnl_lock.
We want to switch to instance locks for most ethtool ops.
Let's add an assert that ops locked devices don't use phydev
today. If one does we can either opt the phy ops out of
being purely ops locked, or do deeper surgery to make phy
locking ops-compatible. I don't think there's any fundamental
challenge to make that work.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/phy_link_topology.h | 5 +++++
drivers/net/phy/phy_link_topology.c | 8 ++++++++
net/ethtool/netlink.c | 6 ++++--
net/ethtool/phy.c | 1 -
4 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h
index 68a59e25821c..95575f68d5bc 100644
--- a/include/linux/phy_link_topology.h
+++ b/include/linux/phy_link_topology.h
@@ -36,6 +36,11 @@ struct phy_device_node {
struct phy_device *phy;
};
+static inline bool phy_link_topo_empty(struct net_device *dev)
+{
+ return !dev->link_topo;
+}
+
#if IS_ENABLED(CONFIG_PHYLIB)
int phy_link_topo_add_phy(struct net_device *dev,
struct phy_device *phy,
diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c
index 1f1eb5d59b38..aed3b26c1674 100644
--- a/drivers/net/phy/phy_link_topology.c
+++ b/drivers/net/phy/phy_link_topology.c
@@ -10,6 +10,7 @@
#include <linux/phy.h>
#include <linux/rtnetlink.h>
#include <linux/xarray.h>
+#include <net/netdev_lock.h>
static int netdev_alloc_phy_link_topology(struct net_device *dev)
{
@@ -35,6 +36,13 @@ int phy_link_topo_add_phy(struct net_device *dev,
struct phy_device_node *pdn;
int ret;
+ /* ethtool ops may run without rtnl_lock, and rtnl_lock is what
+ * currently protects the PHY topology. No driver currently mixes
+ * the two, flag if someone tries. See also ethnl_req_get_phydev().
+ */
+ if (WARN_ON_ONCE(netdev_need_ops_lock(dev)))
+ return -EOPNOTSUPP;
+
if (!topo) {
ret = netdev_alloc_phy_link_topology(dev);
if (ret)
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 902ff7d0a71d..a91cc4bc964f 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -226,11 +226,13 @@ struct phy_device *ethnl_req_get_phydev(const struct ethnl_req_info *req_info,
{
struct phy_device *phydev;
- ASSERT_RTNL();
-
if (!req_info->dev)
return NULL;
+ /* If there is no PHY in sight there's no need for assert locking */
+ if (!phy_link_topo_empty(req_info->dev))
+ ASSERT_RTNL();
+
if (!req_info->phy_index)
return req_info->dev->phydev;
diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c
index ddc6eab701ed..018b0412be86 100644
--- a/net/ethtool/phy.c
+++ b/net/ethtool/phy.c
@@ -78,7 +78,6 @@ static int phy_prepare_data(const struct ethnl_req_info *req_info,
struct phy_device *phydev;
int ret;
- /* RTNL is held by the caller */
phydev = ethnl_req_get_phydev(req_info, tb, ETHTOOL_A_PHY_HEADER,
info->extack);
if (IS_ERR_OR_NULL(phydev))
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion
2026-05-28 23:16 ` [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion Jakub Kicinski
@ 2026-05-29 8:43 ` Maxime Chevallier
2026-05-29 14:27 ` Jakub Kicinski
0 siblings, 1 reply; 19+ messages in thread
From: Maxime Chevallier @ 2026-05-29 8:43 UTC (permalink / raw)
To: Jakub Kicinski, davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, willemb, ernis, sdf.kernel,
kory.maincent, danieller, idosch
Hi,
On 5/29/26 01:16, Jakub Kicinski wrote:
> phydev <> netdev linking and lifecycle depends on rtnl_lock.
> We want to switch to instance locks for most ethtool ops.
> Let's add an assert that ops locked devices don't use phydev
> today. If one does we can either opt the phy ops out of
> being purely ops locked, or do deeper surgery to make phy
> locking ops-compatible. I don't think there's any fundamental
> challenge to make that work.
Yeah untangling phylink/SFP/phylib from rtnl will be needed soon
indeed, quite the can of worms...
But for the topo part, this change should do the trick.
At some point we'll need to convert a more embeded-oriented MAC
driver to being ops-locked, to get a good idea of the amount of
work in front of us.
Most of the constraints come from the different lifetimes of
phy_device, sfp-bus and net_device, RTNL is the easy way to
guarantee that the netdev doesn't dissapear under our feet.
Andrew and Russell were careful to get people to annotate
the RTNL dependencies with assertions, this should make it easier
to tackle the conversion :)
For now,
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Maxime
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
> include/linux/phy_link_topology.h | 5 +++++
> drivers/net/phy/phy_link_topology.c | 8 ++++++++
> net/ethtool/netlink.c | 6 ++++--
> net/ethtool/phy.c | 1 -
> 4 files changed, 17 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h
> index 68a59e25821c..95575f68d5bc 100644
> --- a/include/linux/phy_link_topology.h
> +++ b/include/linux/phy_link_topology.h
> @@ -36,6 +36,11 @@ struct phy_device_node {
> struct phy_device *phy;
> };
>
> +static inline bool phy_link_topo_empty(struct net_device *dev)
> +{
> + return !dev->link_topo;
> +}
> +
> #if IS_ENABLED(CONFIG_PHYLIB)
> int phy_link_topo_add_phy(struct net_device *dev,
> struct phy_device *phy,
> diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c
> index 1f1eb5d59b38..aed3b26c1674 100644
> --- a/drivers/net/phy/phy_link_topology.c
> +++ b/drivers/net/phy/phy_link_topology.c
> @@ -10,6 +10,7 @@
> #include <linux/phy.h>
> #include <linux/rtnetlink.h>
> #include <linux/xarray.h>
> +#include <net/netdev_lock.h>
>
> static int netdev_alloc_phy_link_topology(struct net_device *dev)
> {
> @@ -35,6 +36,13 @@ int phy_link_topo_add_phy(struct net_device *dev,
> struct phy_device_node *pdn;
> int ret;
>
> + /* ethtool ops may run without rtnl_lock, and rtnl_lock is what
> + * currently protects the PHY topology. No driver currently mixes
> + * the two, flag if someone tries. See also ethnl_req_get_phydev().
> + */
> + if (WARN_ON_ONCE(netdev_need_ops_lock(dev)))
> + return -EOPNOTSUPP;
> +
> if (!topo) {
> ret = netdev_alloc_phy_link_topology(dev);
> if (ret)
> diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
> index 902ff7d0a71d..a91cc4bc964f 100644
> --- a/net/ethtool/netlink.c
> +++ b/net/ethtool/netlink.c
> @@ -226,11 +226,13 @@ struct phy_device *ethnl_req_get_phydev(const struct ethnl_req_info *req_info,
> {
> struct phy_device *phydev;
>
> - ASSERT_RTNL();
> -
> if (!req_info->dev)
> return NULL;
>
> + /* If there is no PHY in sight there's no need for assert locking */
> + if (!phy_link_topo_empty(req_info->dev))
> + ASSERT_RTNL();
> +
> if (!req_info->phy_index)
> return req_info->dev->phydev;
>
> diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c
> index ddc6eab701ed..018b0412be86 100644
> --- a/net/ethtool/phy.c
> +++ b/net/ethtool/phy.c
> @@ -78,7 +78,6 @@ static int phy_prepare_data(const struct ethnl_req_info *req_info,
> struct phy_device *phydev;
> int ret;
>
> - /* RTNL is held by the caller */
> phydev = ethnl_req_get_phydev(req_info, tb, ETHTOOL_A_PHY_HEADER,
> info->extack);
> if (IS_ERR_OR_NULL(phydev))
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion
2026-05-29 8:43 ` Maxime Chevallier
@ 2026-05-29 14:27 ` Jakub Kicinski
0 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-29 14:27 UTC (permalink / raw)
To: Maxime Chevallier
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms,
michael.chan, joshwash, tariqt, haiyangz, linux, willemb, ernis,
sdf.kernel, kory.maincent, danieller, idosch
On Fri, 29 May 2026 10:43:04 +0200 Maxime Chevallier wrote:
> On 5/29/26 01:16, Jakub Kicinski wrote:
> > phydev <> netdev linking and lifecycle depends on rtnl_lock.
> > We want to switch to instance locks for most ethtool ops.
> > Let's add an assert that ops locked devices don't use phydev
> > today. If one does we can either opt the phy ops out of
> > being purely ops locked, or do deeper surgery to make phy
> > locking ops-compatible. I don't think there's any fundamental
> > challenge to make that work.
>
> Yeah untangling phylink/SFP/phylib from rtnl will be needed soon
> indeed, quite the can of worms...
>
> But for the topo part, this change should do the trick.
>
> At some point we'll need to convert a more embeded-oriented MAC
> driver to being ops-locked, to get a good idea of the amount of
> work in front of us.
>
> Most of the constraints come from the different lifetimes of
> phy_device, sfp-bus and net_device, RTNL is the easy way to
> guarantee that the netdev doesn't dissapear under our feet.
True, I was thinking about it from an integrated driver perspective
where the same driver spawns all elements. In that case it's mostly
a matter of pointing them all to the right netdev instance lock.
But we'll figure it out :)
> Andrew and Russell were careful to get people to annotate
> the RTNL dependencies with assertions, this should make it easier
> to tackle the conversion :)
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 05/14] net: ethtool: make dev->hwprov ops-protected
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (3 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 06/14] net: ethtool: optionally skip rtnl_lock on Netlink path for GET ops Jakub Kicinski
` (9 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
dev->hwprov tracks the active hwtstamp provider for the device.
Make it ops protected (instance lock if the netdev driver opts
into holding instance lock around callbacks, otherwise rtnl_lock).
hwprov is written in
- drivers/net/phy/phy_device.c
phydev and ops protection don't currently mix, add a comment
- net/ethtool/
as of now holds both rtnl lock and ops lock, this one will
soon only hold one lock or the other
read in:
- net/core/dev_ioctl.c
holds both rtnl lock and ops lock
- net/core/timestamping.c
RCU reader
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/netdevice.h | 3 +++
include/net/netdev_lock.h | 11 +++++++++++
drivers/net/phy/phy_device.c | 3 +++
drivers/net/phy/phy_link_topology.c | 4 +++-
net/core/dev_ioctl.c | 4 ++--
net/ethtool/tsconfig.c | 10 ++++++----
6 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 7309467d7873..2d9a658e9b1a 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2584,6 +2584,9 @@ struct net_device {
* Double protects:
* @up, @moving_ns, @nd_net, @xdp_features
*
+ * Ops protects:
+ * @hwprov
+ *
* Double ops protects:
* @real_num_rx_queues, @real_num_tx_queues
*
diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h
index f4c77899fb86..3eca5465a8e6 100644
--- a/include/net/netdev_lock.h
+++ b/include/net/netdev_lock.h
@@ -102,6 +102,14 @@ static inline void netdev_unlock_ops_compat(struct net_device *dev)
rtnl_unlock();
}
+/* Matching "ops protected" category from netdevice.h */
+static inline int netdev_ops_is_locked(const struct net_device *dev)
+{
+ if (netdev_need_ops_lock(dev))
+ return lockdep_is_held(&dev->lock);
+ return lockdep_rtnl_is_held();
+}
+
static inline int netdev_lock_cmp_fn(const struct lockdep_map *a,
const struct lockdep_map *b)
{
@@ -138,6 +146,9 @@ static inline int netdev_lock_cmp_fn(const struct lockdep_map *a,
#define netdev_lock_dereference(p, dev) \
rcu_dereference_protected(p, lockdep_is_held(&(dev)->lock))
+#define netdev_ops_lock_dereference(p, dev) \
+ rcu_dereference_protected(p, netdev_ops_is_locked(dev))
+
int netdev_debug_event(struct notifier_block *nb, unsigned long event,
void *ptr);
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 3370eb822017..ea53e477465d 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1935,6 +1935,9 @@ void phy_detach(struct phy_device *phydev)
if (dev) {
struct hwtstamp_provider *hwprov;
+ /* hwprov may technically be protected by ops lock but
+ * not for devices with a phydev, see phy_link_topo_add_phy()
+ */
hwprov = rtnl_dereference(dev->hwprov);
/* Disable timestamp if it is the one selected */
if (hwprov && hwprov->phydev == phydev) {
diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c
index aed3b26c1674..4134de7ae313 100644
--- a/drivers/net/phy/phy_link_topology.c
+++ b/drivers/net/phy/phy_link_topology.c
@@ -38,7 +38,9 @@ int phy_link_topo_add_phy(struct net_device *dev,
/* ethtool ops may run without rtnl_lock, and rtnl_lock is what
* currently protects the PHY topology. No driver currently mixes
- * the two, flag if someone tries. See also ethnl_req_get_phydev().
+ * the two, flag if someone tries. See also:
+ * - ethnl_req_get_phydev()
+ * - phy_detach()
*/
if (WARN_ON_ONCE(netdev_need_ops_lock(dev)))
return -EOPNOTSUPP;
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index f3979b276090..a320e264eaaf 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -260,7 +260,7 @@ int dev_get_hwtstamp_phylib(struct net_device *dev,
{
struct hwtstamp_provider *hwprov;
- hwprov = rtnl_dereference(dev->hwprov);
+ hwprov = netdev_ops_lock_dereference(dev->hwprov, dev);
if (hwprov) {
cfg->qualifier = hwprov->desc.qualifier;
if (hwprov->source == HWTSTAMP_SOURCE_PHYLIB &&
@@ -337,7 +337,7 @@ int dev_set_hwtstamp_phylib(struct net_device *dev,
bool phy_ts;
int err;
- hwprov = rtnl_dereference(dev->hwprov);
+ hwprov = netdev_ops_lock_dereference(dev->hwprov, dev);
if (hwprov) {
if (hwprov->source == HWTSTAMP_SOURCE_PHYLIB &&
hwprov->phydev) {
diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c
index 664c3fe49b5b..65df05a05ade 100644
--- a/net/ethtool/tsconfig.c
+++ b/net/ethtool/tsconfig.c
@@ -2,6 +2,7 @@
#include <linux/net_tstamp.h>
#include <linux/ptp_clock_kernel.h>
+#include <net/netdev_lock.h>
#include "bitset.h"
#include "common.h"
@@ -57,7 +58,7 @@ static int tsconfig_prepare_data(const struct ethnl_req_info *req_base,
data->hwtst_config.flags = cfg.flags;
data->hwprov_desc.index = -1;
- hwprov = rtnl_dereference(dev->hwprov);
+ hwprov = netdev_ops_lock_dereference(dev->hwprov, dev);
if (hwprov) {
data->hwprov_desc.index = hwprov->desc.index;
data->hwprov_desc.qualifier = hwprov->desc.qualifier;
@@ -213,7 +214,7 @@ static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info)
return -ENOMEM;
}
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
reply_data->base.dev = dev;
ret = tsconfig_prepare_data(&req_info->base, &reply_data->base, info);
if (ret < 0)
@@ -316,7 +317,7 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base,
struct hwtstamp_provider_desc __hwprov_desc = {.index = -1};
struct hwtstamp_provider *__hwprov;
- __hwprov = rtnl_dereference(dev->hwprov);
+ __hwprov = netdev_ops_lock_dereference(dev->hwprov, dev);
if (__hwprov) {
__hwprov_desc.index = __hwprov->desc.index;
__hwprov_desc.qualifier = __hwprov->desc.qualifier;
@@ -414,7 +415,8 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base,
goto err_free_hwprov;
/* Change the selected hwtstamp source */
- __hwprov = rcu_replace_pointer_rtnl(dev->hwprov, hwprov);
+ __hwprov = rcu_replace_pointer(dev->hwprov, hwprov,
+ netdev_ops_is_locked(dev));
if (__hwprov)
kfree_rcu(__hwprov, rcu_head);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 06/14] net: ethtool: optionally skip rtnl_lock on Netlink path for GET ops
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (4 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 05/14] net: ethtool: make dev->hwprov ops-protected Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 07/14] net: ethtool: optionally skip rtnl_lock on Netlink path for SET ops Jakub Kicinski
` (8 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
ethnl_default_doit() and ethnl_default_dump_one() are both used
exclusively for GET callbacks (former to get info for a single
device or get global strings). ops-locked devices don't need
rtnl_lock for GET callbacks, stop taking it.
Introduce an opt-out mechanism for devices which use phylink (fbnic)
since phylink currently depends on rtnl_lock protection. Subsequent
patches will add more exceptions, anyway. Practically the new helpers
for judging if command needs rtnl_lock could also call
netdev_need_ops_lock() but I find that it makes the code in the callers
slightly less obvious.
Add a helper for IOCTLs already, even tho its unused so that
we can keep them in sync as the series progresses.
This is the first user-visible step of moving ethtool ops out
from under rtnl. Subsequent patches do the same for SET ops,
as well as the ioctl path.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/ethtool.h | 18 +++++++-
net/ethtool/common.h | 46 +++++++++++++++++++
.../net/ethernet/meta/fbnic/fbnic_ethtool.c | 2 +
net/ethtool/mm.c | 5 +-
net/ethtool/netlink.c | 19 ++++++--
5 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 0caaeb91b094..f4f933b8be26 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -930,6 +930,13 @@ struct kernel_ethtool_ts_info {
u32 rx_filters;
};
+/* Bits for ethtool_ops::op_needs_rtnl
+ * LINKSETTINGS cover a number of commands, but in most cases we want to keep
+ * these bits separate, per GET and SET. GET is much easier to "unlock".
+ */
+#define ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS BIT(0)
+#define ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM BIT(1)
+
/**
* struct ethtool_ops - optional netdev operations
* @supported_input_xfrm: supported types of input xfrm from %RXH_XFRM_*.
@@ -956,6 +963,14 @@ struct kernel_ethtool_ts_info {
* @supported_coalesce_params: supported types of interrupt coalescing.
* @supported_ring_params: supported ring params.
* @supported_hwtstamp_qualifiers: bitfield of supported hwtstamp qualifier.
+ * @op_needs_rtnl: mask of %ETHTOOL_OP_NEEDS_RTNL_* bits.
+ * For use with ops-locked drivers (ignored otherwise). Selects which
+ * ethtool callbacks driver needs to still be executed under rtnl_lock
+ * (in addition to the netdev instance lock).
+ * The following commonly used core APIs currently require rtnl_lock
+ * (this list may not be exhaustive):
+ * - phylink helpers (note that phydev is currently unsupported!)
+ *
* @get_drvinfo: Report driver/device information. Modern drivers no
* longer have to implement this callback. Most fields are
* correctly filled in by the core using system information, or
@@ -1155,7 +1170,7 @@ struct kernel_ethtool_ts_info {
*
* All operations are optional (i.e. the function pointer may be set
* to %NULL) and callers must take this into account. Callers must
- * hold the RTNL lock.
+ * hold the RTNL lock or netdev instance lock (see @op_needs_rtnl).
*
* See the structures used by these operations for further documentation.
* Note that for all operations using a structure ending with a zero-
@@ -1178,6 +1193,7 @@ struct ethtool_ops {
u32 supported_coalesce_params;
u32 supported_ring_params;
u32 supported_hwtstamp_qualifiers;
+ u32 op_needs_rtnl;
void (*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *);
int (*get_regs_len)(struct net_device *);
void (*get_regs)(struct net_device *, struct ethtool_regs *, void *);
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 1609cf4e53eb..391c41ca56be 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -80,6 +80,52 @@ int ethtool_get_module_eeprom_call(struct net_device *dev,
bool __ethtool_dev_mm_supported(struct net_device *dev);
+/**
+ * ethtool_nl_msg_needs_rtnl() - does this Netlink cmd need rtnl_lock?
+ * @dev: target device
+ * @cmd: ETHTOOL_MSG_* Netlink command value
+ *
+ * Return: true if @cmd is a command for which @dev has opted-in to
+ * keeping rtnl_lock held across the call (via op_needs_rtnl).
+ */
+static inline bool
+ethtool_nl_msg_needs_rtnl(const struct net_device *dev, u8 cmd)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+
+ switch (cmd) {
+ case ETHTOOL_MSG_LINKINFO_GET:
+ case ETHTOOL_MSG_LINKMODES_GET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS;
+ case ETHTOOL_MSG_PAUSE_GET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM;
+ }
+ return false;
+}
+
+/**
+ * ethtool_ioctl_needs_rtnl() - does this legacy ioctl cmd need rtnl_lock?
+ * @dev: target device
+ * @ethcmd: ETHTOOL_* ioctl command value
+ *
+ * Return: true if @ethcmd is a command for which @dev has opted-in to
+ * keeping rtnl_lock held across the call (via op_needs_rtnl).
+ */
+static inline bool
+ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+
+ switch (ethcmd) {
+ case ETHTOOL_GLINKSETTINGS:
+ case ETHTOOL_GSET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS;
+ case ETHTOOL_GPAUSEPARAM:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM;
+ }
+ return false;
+}
+
#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
void ethtool_rss_notify(struct net_device *dev, u32 type, u32 rss_context);
#else
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
index f14de2366854..a2c16d599389 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
@@ -2020,6 +2020,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = {
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
ETHTOOL_RING_USE_HDS_THRS,
.rxfh_max_num_contexts = FBNIC_RPC_RSS_TBL_COUNT,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS |
+ ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM,
.get_drvinfo = fbnic_get_drvinfo,
.get_regs_len = fbnic_get_regs_len,
.get_regs = fbnic_get_regs,
diff --git a/net/ethtool/mm.c b/net/ethtool/mm.c
index 29bbbc149375..7092f1ce8612 100644
--- a/net/ethtool/mm.c
+++ b/net/ethtool/mm.c
@@ -246,8 +246,9 @@ const struct ethnl_request_ops ethnl_mm_request_ops = {
};
/* Returns whether a given device supports the MAC merge layer
- * (has an eMAC and a pMAC). Must be called under rtnl_lock() and
- * ethnl_ops_begin().
+ * (has an eMAC and a pMAC). Must be called under whichever lock
+ * netdev_ops_assert_locked() accepts (rtnl for traditional drivers,
+ * the netdev instance lock for ops-locked ones) and ethnl_ops_begin().
*/
bool __ethtool_dev_mm_supported(struct net_device *dev)
{
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index a91cc4bc964f..ddb35790063d 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -7,6 +7,7 @@
#include <linux/phy_link_topology.h>
#include <linux/pm_runtime.h>
+#include "common.h"
#include "module_fw.h"
#include "netlink.h"
@@ -509,6 +510,7 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
struct ethnl_req_info *req_info = NULL;
const u8 cmd = info->genlhdr->cmd;
const struct ethnl_request_ops *ops;
+ bool need_rtnl = false;
int hdr_len, reply_len;
struct sk_buff *rskb;
void *reply_payload;
@@ -535,13 +537,17 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
ethnl_init_reply_data(reply_data, ops, req_info->dev);
if (req_info->dev) {
- rtnl_lock();
+ need_rtnl = !netdev_need_ops_lock(req_info->dev) ||
+ ethtool_nl_msg_needs_rtnl(req_info->dev, cmd);
+ if (need_rtnl)
+ rtnl_lock();
netdev_lock_ops(req_info->dev);
}
ret = ops->prepare_data(req_info, reply_data, info);
if (req_info->dev) {
netdev_unlock_ops(req_info->dev);
- rtnl_unlock();
+ if (need_rtnl)
+ rtnl_unlock();
}
if (ret < 0)
goto err_dev;
@@ -589,6 +595,7 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
const struct ethnl_dump_ctx *ctx,
const struct genl_info *info)
{
+ bool need_rtnl;
void *ehdr;
int ret;
@@ -599,11 +606,15 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
return -EMSGSIZE;
ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
- rtnl_lock();
+ need_rtnl = !netdev_need_ops_lock(dev) ||
+ ethtool_nl_msg_needs_rtnl(dev, ctx->ops->request_cmd);
+ if (need_rtnl)
+ rtnl_lock();
netdev_lock_ops(dev);
ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info);
netdev_unlock_ops(dev);
- rtnl_unlock();
+ if (need_rtnl)
+ rtnl_unlock();
if (ret < 0)
goto out_cancel;
ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr);
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 07/14] net: ethtool: optionally skip rtnl_lock on Netlink path for SET ops
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (5 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 06/14] net: ethtool: optionally skip rtnl_lock on Netlink path for GET ops Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 08/14] net: ethtool: optionally skip rtnl_lock in cable test handlers Jakub Kicinski
` (7 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Make ethtool not take rtnl_lock for SET commands when operation
is performed on an ops-locked driver. cfg/cfg_pending are now
ops-locked, since only ethtool modifies them.
Some SET driver callbacks will still need rtnl_lock, most notably
those which may end up calling netdev_update_features() or the qdisc
layer (via netif_set_real_num_tx_queues()). Let drivers selectively
opt back into the rtnl_lock with a new bitfield in ops.
We need two helpers since Netlink and ioctl cmds have different
values. Keep the helpers side by side in common.h to make sure
they get updated together, even tho they will only get called
from ioctl.c and netlink.c.
SET commands which don't use ethnl_default_set_doit() are converted
by subsequent commits.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/ethtool.h | 8 +++++
include/linux/netdevice.h | 2 +-
net/ethtool/common.h | 30 +++++++++++++++++++
.../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 4 +++
drivers/net/ethernet/google/gve/gve_ethtool.c | 2 ++
.../ethernet/mellanox/mlx5/core/en_ethtool.c | 3 ++
.../net/ethernet/mellanox/mlx5/core/en_rep.c | 2 ++
.../mellanox/mlx5/core/ipoib/ethtool.c | 2 ++
.../net/ethernet/meta/fbnic/fbnic_ethtool.c | 5 +++-
.../ethernet/microsoft/mana/mana_ethtool.c | 2 ++
drivers/net/netdevsim/ethtool.c | 1 +
net/ethtool/netlink.c | 9 ++++--
12 files changed, 66 insertions(+), 4 deletions(-)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index f4f933b8be26..4f15221119e2 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -936,6 +936,12 @@ struct kernel_ethtool_ts_info {
*/
#define ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS BIT(0)
#define ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM BIT(1)
+#define ETHTOOL_OP_NEEDS_RTNL_SPFLAGS BIT(2)
+#define ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM BIT(3)
+#define ETHTOOL_OP_NEEDS_RTNL_SCHANNELS BIT(4)
+#define ETHTOOL_OP_NEEDS_RTNL_SCOALESCE BIT(5)
+#define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6)
+#define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7)
/**
* struct ethtool_ops - optional netdev operations
@@ -970,6 +976,8 @@ struct kernel_ethtool_ts_info {
* The following commonly used core APIs currently require rtnl_lock
* (this list may not be exhaustive):
* - phylink helpers (note that phydev is currently unsupported!)
+ * - netdev_update_features()
+ * - netif_set_real_num_tx_queues()
*
* @get_drvinfo: Report driver/device information. Modern drivers no
* longer have to implement this callback. Most fields are
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 2d9a658e9b1a..08db7305e0f9 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2585,7 +2585,7 @@ struct net_device {
* @up, @moving_ns, @nd_net, @xdp_features
*
* Ops protects:
- * @hwprov
+ * @cfg, @cfg_pending, @hwprov
*
* Double ops protects:
* @real_num_rx_queues, @real_num_tx_queues
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 391c41ca56be..e3052972f953 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -95,10 +95,24 @@ ethtool_nl_msg_needs_rtnl(const struct net_device *dev, u8 cmd)
switch (cmd) {
case ETHTOOL_MSG_LINKINFO_GET:
+ case ETHTOOL_MSG_LINKINFO_SET:
case ETHTOOL_MSG_LINKMODES_GET:
+ case ETHTOOL_MSG_LINKMODES_SET:
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS;
+ case ETHTOOL_MSG_PRIVFLAGS_SET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPFLAGS;
+ case ETHTOOL_MSG_RINGS_SET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM;
+ case ETHTOOL_MSG_CHANNELS_SET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCHANNELS;
+ case ETHTOOL_MSG_COALESCE_SET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCOALESCE;
case ETHTOOL_MSG_PAUSE_GET:
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM;
+ case ETHTOOL_MSG_PAUSE_SET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM;
+ case ETHTOOL_MSG_RSS_SET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS;
}
return false;
}
@@ -119,9 +133,25 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd)
switch (ethcmd) {
case ETHTOOL_GLINKSETTINGS:
case ETHTOOL_GSET:
+ case ETHTOOL_SLINKSETTINGS:
+ case ETHTOOL_SSET:
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS;
+ case ETHTOOL_SPFLAGS:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPFLAGS;
+ case ETHTOOL_SRINGPARAM:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM;
+ case ETHTOOL_SCHANNELS:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCHANNELS;
+ case ETHTOOL_SCOALESCE:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCOALESCE;
case ETHTOOL_GPAUSEPARAM:
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM;
+ case ETHTOOL_SPAUSEPARAM:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM;
+ case ETHTOOL_SRSSH:
+ case ETHTOOL_SRXFH:
+ case ETHTOOL_SRXFHINDIR:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS;
}
return false;
}
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
index 9b14134d62d2..c8a87ed9283e 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
@@ -5729,6 +5729,10 @@ const struct ethtool_ops bnxt_ethtool_ops = {
.rxfh_max_num_contexts = BNXT_MAX_ETH_RSS_CTX + 1,
.rxfh_indir_space = BNXT_MAX_RSS_TABLE_ENTRIES_P5,
.rxfh_priv_size = sizeof(struct bnxt_rss_ctx),
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
+ ETHTOOL_OP_NEEDS_RTNL_SCOALESCE |
+ ETHTOOL_OP_NEEDS_RTNL_RSS,
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USECS_IRQ |
diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c
index dc2213b5ce24..f4b2b14237ba 100644
--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
+++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
@@ -983,6 +983,8 @@ const struct ethtool_ops gve_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS,
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
ETHTOOL_RING_USE_RX_BUF_LEN,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM,
.get_drvinfo = gve_get_drvinfo,
.get_strings = gve_get_strings,
.get_sset_count = gve_get_sset_count,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c
index 61993485e451..2f5b626ba33f 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c
@@ -2719,6 +2719,9 @@ const struct ethtool_ops mlx5e_ethtool_ops = {
.rxfh_per_ctx_fields = true,
.rxfh_per_ctx_key = true,
.rxfh_max_num_contexts = MLX5E_MAX_NUM_RSS,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
+ ETHTOOL_OP_NEEDS_RTNL_SPFLAGS,
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USE_ADAPTIVE |
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
index ba6c0f38cc73..1a8a19f980d3 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
@@ -418,6 +418,8 @@ static const struct ethtool_ops mlx5e_rep_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USE_ADAPTIVE,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM,
.get_drvinfo = mlx5e_rep_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_strings = mlx5e_rep_get_strings,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
index 3b2f54ca30a8..9b3b32408c64 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
@@ -285,6 +285,8 @@ const struct ethtool_ops mlx5i_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USE_ADAPTIVE,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM,
.get_drvinfo = mlx5i_get_drvinfo,
.get_strings = mlx5i_get_strings,
.get_sset_count = mlx5i_get_sset_count,
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
index a2c16d599389..2e5a4a0ff972 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
@@ -2021,7 +2021,10 @@ static const struct ethtool_ops fbnic_ethtool_ops = {
ETHTOOL_RING_USE_HDS_THRS,
.rxfh_max_num_contexts = FBNIC_RPC_RSS_TBL_COUNT,
.op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS |
- ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM,
+ ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM |
+ ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
+ ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM,
.get_drvinfo = fbnic_get_drvinfo,
.get_regs_len = fbnic_get_regs_len,
.get_regs = fbnic_get_regs,
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 04350973e19e..55df44d728a0 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -575,6 +575,8 @@ static int mana_get_link_ksettings(struct net_device *ndev,
const struct ethtool_ops mana_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
+ ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM,
.get_ethtool_stats = mana_get_ethtool_stats,
.get_sset_count = mana_get_sset_count,
.get_strings = mana_get_strings,
diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 36a201533aae..9350ba48eb81 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -209,6 +209,7 @@ static const struct ethtool_ops nsim_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS,
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
ETHTOOL_RING_USE_HDS_THRS,
+ .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS,
.get_pause_stats = nsim_get_pause_stats,
.get_pauseparam = nsim_get_pauseparam,
.set_pauseparam = nsim_set_pauseparam,
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index ddb35790063d..16f6d7f81502 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -903,6 +903,7 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
const u8 cmd = info->genlhdr->cmd;
struct ethnl_req_info *req_info;
struct net_device *dev;
+ bool need_rtnl;
int ret;
ops = ethnl_default_requests[cmd];
@@ -927,8 +928,11 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
}
dev = req_info->dev;
+ need_rtnl = !netdev_need_ops_lock(dev) ||
+ ethtool_nl_msg_needs_rtnl(dev, cmd);
- rtnl_lock();
+ if (need_rtnl)
+ rtnl_lock();
netdev_lock_ops(dev);
dev->cfg_pending = kmemdup(dev->cfg, sizeof(*dev->cfg),
GFP_KERNEL_ACCOUNT);
@@ -958,7 +962,8 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
out_tie_cfg:
dev->cfg_pending = dev->cfg;
netdev_unlock_ops(dev);
- rtnl_unlock();
+ if (need_rtnl)
+ rtnl_unlock();
out_dev:
ethnl_parse_header_dev_put(req_info);
out_free_req:
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 08/14] net: ethtool: optionally skip rtnl_lock in cable test handlers
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (6 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 07/14] net: ethtool: optionally skip rtnl_lock on Netlink path for SET ops Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 09/14] net: ethtool: optionally skip rtnl_lock in ethnl_tsinfo_dumpit() Jakub Kicinski
` (6 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Skip rtnl_lock in cable test handlers. This is really a noop since
no ops locked device supports these.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/ethtool/cabletest.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/net/ethtool/cabletest.c b/net/ethtool/cabletest.c
index 8d375dac2a40..9c22d4c767c6 100644
--- a/net/ethtool/cabletest.c
+++ b/net/ethtool/cabletest.c
@@ -73,8 +73,7 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;
- rtnl_lock();
- netdev_lock_ops(dev);
+ netdev_lock_ops_compat(dev);
phydev = ethnl_req_get_phydev(&req_info, tb,
ETHTOOL_A_CABLE_TEST_HEADER,
info->extack);
@@ -101,8 +100,7 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
ethnl_cable_test_started(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);
out_unlock:
- netdev_unlock_ops(dev);
- rtnl_unlock();
+ netdev_unlock_ops_compat(dev);
ethnl_parse_header_dev_put(&req_info);
return ret;
}
@@ -342,8 +340,7 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
if (ret)
goto out_dev_put;
- rtnl_lock();
- netdev_lock_ops(dev);
+ netdev_lock_ops_compat(dev);
phydev = ethnl_req_get_phydev(&req_info, tb,
ETHTOOL_A_CABLE_TEST_TDR_HEADER,
info->extack);
@@ -371,8 +368,7 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
out_unlock:
- netdev_unlock_ops(dev);
- rtnl_unlock();
+ netdev_unlock_ops_compat(dev);
out_dev_put:
ethnl_parse_header_dev_put(&req_info);
return ret;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 09/14] net: ethtool: optionally skip rtnl_lock in ethnl_tsinfo_dumpit()
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (7 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 08/14] net: ethtool: optionally skip rtnl_lock in cable test handlers Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 10/14] net: ethtool: optionally skip rtnl_lock in ethnl_act_module_fw_flash() Jakub Kicinski
` (5 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
ethnl_tsinfo_dumpit() iterates netdevs and per-netdev PHY topology
calling ops->get_ts_info(). Switch to the "ops compat locking"
helpers which take either rtnl_lock or instance lock, depending
on what the device needs.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/ethtool/tsinfo.c | 32 +++++++++++++++-----------------
1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/net/ethtool/tsinfo.c b/net/ethtool/tsinfo.c
index 14bf01e3b55c..c9b680a9cc3f 100644
--- a/net/ethtool/tsinfo.c
+++ b/net/ethtool/tsinfo.c
@@ -6,6 +6,7 @@
#include <linux/ptp_clock_kernel.h>
#include <net/netdev_lock.h>
+#include "../core/dev.h"
#include "bitset.h"
#include "common.h"
#include "netlink.h"
@@ -473,28 +474,25 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct ethnl_tsinfo_dump_ctx *ctx = (void *)cb->ctx;
struct net *net = sock_net(skb->sk);
- struct net_device *dev;
int ret = 0;
- rtnl_lock();
if (ctx->req_info->base.dev) {
- dev = ctx->req_info->base.dev;
- netdev_lock_ops(dev);
+ struct net_device *dev = ctx->req_info->base.dev;
+
+ netdev_lock_ops_compat(dev);
ret = ethnl_tsinfo_dump_one_net_topo(skb, dev, cb);
- netdev_unlock_ops(dev);
- } else {
- for_each_netdev_dump(net, dev, ctx->pos_ifindex) {
- netdev_lock_ops(dev);
- ret = ethnl_tsinfo_dump_one_net_topo(skb, dev, cb);
- netdev_unlock_ops(dev);
- if (ret < 0 && ret != -EOPNOTSUPP)
- break;
- ctx->pos_phyindex = 0;
- ctx->netdev_dump_done = false;
- ctx->pos_phcqualifier = HWTSTAMP_PROVIDER_QUALIFIER_PRECISE;
- }
+ netdev_unlock_ops_compat(dev);
+ return ret;
+ }
+
+ for_each_netdev_lock_ops_compat_scoped(net, dev, ctx->pos_ifindex) {
+ ret = ethnl_tsinfo_dump_one_net_topo(skb, dev, cb);
+ if (ret < 0 && ret != -EOPNOTSUPP)
+ break;
+ ctx->pos_phyindex = 0;
+ ctx->netdev_dump_done = false;
+ ctx->pos_phcqualifier = HWTSTAMP_PROVIDER_QUALIFIER_PRECISE;
}
- rtnl_unlock();
return ret;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 10/14] net: ethtool: optionally skip rtnl_lock in ethnl_act_module_fw_flash()
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (8 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 09/14] net: ethtool: optionally skip rtnl_lock in ethnl_tsinfo_dumpit() Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 11/14] net: ethtool: optionally skip rtnl_lock in RSS context handlers Jakub Kicinski
` (4 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Module firmware flashing reads SFF-8024 identifier bytes via
.get_module_eeprom_by_page(). Other than that it modifies
a bit in the netdev->ethtool struct. Both should be ops-locked
at this point.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/netdevice.h | 2 +-
net/ethtool/module.c | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 08db7305e0f9..db24e5610774 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2585,7 +2585,7 @@ struct net_device {
* @up, @moving_ns, @nd_net, @xdp_features
*
* Ops protects:
- * @cfg, @cfg_pending, @hwprov
+ * @cfg, @cfg_pending, @ethtool, @hwprov
*
* Double ops protects:
* @real_num_rx_queues, @real_num_tx_queues
diff --git a/net/ethtool/module.c b/net/ethtool/module.c
index c3388e6d7ec8..9cf670e089f2 100644
--- a/net/ethtool/module.c
+++ b/net/ethtool/module.c
@@ -429,8 +429,7 @@ int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info)
return ret;
dev = req_info.dev;
- rtnl_lock();
- netdev_lock_ops(dev);
+ netdev_lock_ops_compat(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_unlock;
@@ -445,8 +444,7 @@ int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info)
ethnl_ops_complete(dev);
out_unlock:
- netdev_unlock_ops(dev);
- rtnl_unlock();
+ netdev_unlock_ops_compat(dev);
ethnl_parse_header_dev_put(&req_info);
return ret;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 11/14] net: ethtool: optionally skip rtnl_lock in RSS context handlers
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (9 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 10/14] net: ethtool: optionally skip rtnl_lock in ethnl_act_module_fw_flash() Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 12/14] net: ethtool: ioctl: concentrate the locking Jakub Kicinski
` (3 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Skip rtnl_lock in RSS context handlers if device is ops-locked.
Fairly trivial conversion. bnxt needed rtnl_lock for changing
the main context but looks like additional contexts are fine
without it.
Note (for review bots?) that ethnl_ops_begin() checks whether
the device is still registered.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
| 20 ++++++--------------
1 file changed, 6 insertions(+), 14 deletions(-)
--git a/net/ethtool/rss.c b/net/ethtool/rss.c
index 65bad23d5c59..d8adc78e3775 100644
--- a/net/ethtool/rss.c
+++ b/net/ethtool/rss.c
@@ -2,6 +2,7 @@
#include <net/netdev_lock.h>
+#include "../core/dev.h"
#include "common.h"
#include "netlink.h"
@@ -468,21 +469,16 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
struct net *net = sock_net(skb->sk);
- struct net_device *dev;
int ret = 0;
- rtnl_lock();
- for_each_netdev_dump(net, dev, ctx->ifindex) {
+ for_each_netdev_lock_ops_compat_scoped(net, dev, ctx->ifindex) {
if (ctx->match_ifindex && ctx->match_ifindex != ctx->ifindex)
break;
- netdev_lock_ops(dev);
ret = rss_dump_one_dev(skb, cb, dev);
- netdev_unlock_ops(dev);
if (ret)
break;
}
- rtnl_unlock();
return ret;
}
@@ -1037,8 +1033,7 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
if (ret)
goto exit_free_dev;
- rtnl_lock();
- netdev_lock_ops(dev);
+ netdev_lock_ops_compat(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
@@ -1125,8 +1120,7 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
exit_ops:
ethnl_ops_complete(dev);
exit_dev_unlock:
- netdev_unlock_ops(dev);
- rtnl_unlock();
+ netdev_unlock_ops_compat(dev);
exit_free_dev:
ethnl_parse_header_dev_put(&req.base);
exit_free_rsp:
@@ -1179,8 +1173,7 @@ int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info)
goto exit_free_dev;
}
- rtnl_lock();
- netdev_lock_ops(dev);
+ netdev_lock_ops_compat(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
@@ -1210,8 +1203,7 @@ int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info)
mutex_unlock(&dev->ethtool->rss_lock);
ethnl_ops_complete(dev);
exit_dev_unlock:
- netdev_unlock_ops(dev);
- rtnl_unlock();
+ netdev_unlock_ops_compat(dev);
exit_free_dev:
ethnl_parse_header_dev_put(&req);
return ret;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 12/14] net: ethtool: ioctl: concentrate the locking
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (10 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 11/14] net: ethtool: optionally skip rtnl_lock in RSS context handlers Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 13/14] net: ethtool: optionally skip rtnl_lock on IOCTL path Jakub Kicinski
` (2 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Add another layer of helper functions to make upcoming locking
changes easier. Otherwise we'd need a pretty complex goto
structure. netdev instance lock is now taken slightly sooner
but that should not be an issue since rtnl_lock is already held,
anyway.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/ethtool/ioctl.c | 35 +++++++++++++++++++++++++----------
1 file changed, 25 insertions(+), 10 deletions(-)
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 1d74ee67e77a..6c3a7e8644ae 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -3257,18 +3257,14 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr)
/* The main entry point in this file. Called from net/core/dev_ioctl.c */
static int
-__dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
- u32 ethcmd, struct ethtool_devlink_compat *devlink_state)
+dev_ethtool_locked(struct net *net, struct net_device *dev,
+ void __user *useraddr,
+ u32 ethcmd, struct ethtool_devlink_compat *devlink_state)
{
- struct net_device *dev;
u32 sub_cmd;
int rc;
netdev_features_t old_features;
- dev = __dev_get_by_name(net, ifr->ifr_name);
- if (!dev)
- return -ENODEV;
-
if (ethcmd == ETHTOOL_PERQUEUE) {
if (copy_from_user(&sub_cmd, useraddr + sizeof(ethcmd), sizeof(sub_cmd)))
return -EFAULT;
@@ -3319,7 +3315,6 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
return -EPERM;
}
- netdev_lock_ops(dev);
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);
@@ -3559,7 +3554,29 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
out:
if (dev->dev.parent)
pm_runtime_put(dev->dev.parent);
+
+ return rc;
+}
+
+static int
+__dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
+ u32 ethcmd, struct ethtool_devlink_compat *devlink_state)
+{
+ struct net_device *dev;
+ int rc;
+
+ rtnl_lock();
+ dev = __dev_get_by_name(net, ifr->ifr_name);
+ if (!dev) {
+ rc = -ENODEV;
+ goto exit_rtnl_unlock;
+ }
+
+ netdev_lock_ops(dev);
+ rc = dev_ethtool_locked(net, dev, useraddr, ethcmd, devlink_state);
netdev_unlock_ops(dev);
+exit_rtnl_unlock:
+ rtnl_unlock();
return rc;
}
@@ -3587,9 +3604,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
break;
}
- rtnl_lock();
rc = __dev_ethtool(net, ifr, useraddr, ethcmd, state);
- rtnl_unlock();
if (rc)
goto exit_free;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 13/14] net: ethtool: optionally skip rtnl_lock on IOCTL path
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (11 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 12/14] net: ethtool: ioctl: concentrate the locking Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-28 23:16 ` [PATCH net-next 14/14] docs: net: ethtool: document ops-locked drivers and op_needs_rtnl Jakub Kicinski
2026-05-29 7:41 ` [syzbot ci] Re: net: ethtool: let ops locked drivers run without rtnl_lock syzbot ci
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Convert the IOCTL path similarly to how we converted Netlink.
The device lookup gets a little hairy. We could take rtnl_lock
unconditionally and drop it before calling the driver (this would
avoid the reference + liveness check). But I think being able
to make progress even if rtnl is dead-locked is quite useful.
First extra concern is handling features. List all the cmds which
modify features and always take rtnl_lock. We could fold this list
into ethtool_ioctl_needs_rtnl() but seems cleaner to keep
ethtool_ioctl_needs_rtnl() driver-related. If a driver changed
features and we were not holding rtnl_lock - warn about it.
It can only happen on buggy ops locked drivers (buggy because
they should have set appropriate "I need rtnl for op X" bit).
Second wrinkle is the PHY ID hack which drops the locks while
sleeping. Convert its static "busy" variable which used to
be protected by rtnl_lock to a field in struct ethtool_netdev_state.
This feature is about identifying an adapter or a port within
a system, so being able to blink multiple LEDs at the same
time is likely not very useful in practice. But it's the simplest
fix, we can add a mutex if someone thinks a system should only
be ID'ing one port at a time.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/linux/ethtool.h | 2 +
net/ethtool/ioctl.c | 98 ++++++++++++++++++++++++++++++-----------
2 files changed, 74 insertions(+), 26 deletions(-)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 4f15221119e2..35ee57a0e5fa 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -1375,6 +1375,7 @@ int ethtool_virtdev_set_link_ksettings(struct net_device *dev,
* within RTNL.
* @rss_indir_user_size: Number of user provided entries for the default
* (context 0) indirection table.
+ * @phys_id_busy: Loop blinking the device LED is running.
* @wol_enabled: Wake-on-LAN is enabled
* @module_fw_flash_in_progress: Module firmware flashing is in progress.
*/
@@ -1382,6 +1383,7 @@ struct ethtool_netdev_state {
struct xarray rss_ctx;
struct mutex rss_lock;
u32 rss_indir_user_size;
+ unsigned phys_id_busy:1;
unsigned wol_enabled:1;
unsigned module_fw_flash_in_progress:1;
};
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 6c3a7e8644ae..aea087d62fe9 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -543,7 +543,7 @@ static int ethtool_get_link_ksettings(struct net_device *dev,
int err = 0;
struct ethtool_link_ksettings link_ksettings;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
if (!dev->ethtool_ops->get_link_ksettings)
return -EOPNOTSUPP;
@@ -600,7 +600,7 @@ static int ethtool_set_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings link_ksettings = {};
int err;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
if (!dev->ethtool_ops->set_link_ksettings)
return -EOPNOTSUPP;
@@ -674,7 +674,7 @@ static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
struct ethtool_cmd cmd;
int err;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
if (!dev->ethtool_ops->get_link_ksettings)
return -EOPNOTSUPP;
@@ -710,7 +710,7 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
struct ethtool_cmd cmd;
int ret;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
return -EFAULT;
@@ -2451,10 +2451,10 @@ void ethtool_puts(u8 **data, const char *str)
}
EXPORT_SYMBOL(ethtool_puts);
-static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
+static int ethtool_phys_id(struct net_device *dev, void __user *useraddr,
+ bool has_rtnl_lock)
{
struct ethtool_value id;
- static bool busy;
const struct ethtool_ops *ops = dev->ethtool_ops;
netdevice_tracker dev_tracker;
int rc;
@@ -2462,7 +2462,7 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
if (!ops->set_phys_id)
return -EOPNOTSUPP;
- if (busy)
+ if (dev->ethtool->phys_id_busy)
return -EBUSY;
if (copy_from_user(&id, useraddr, sizeof(id)))
@@ -2472,13 +2472,14 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
if (rc < 0)
return rc;
- /* Drop the RTNL lock while waiting, but prevent reentry or
+ /* Drop the locks while waiting, but prevent reentry or
* removal of the device.
*/
- busy = true;
+ dev->ethtool->phys_id_busy = true;
netdev_hold(dev, &dev_tracker, GFP_KERNEL);
netdev_unlock_ops(dev);
- rtnl_unlock();
+ if (has_rtnl_lock)
+ rtnl_unlock();
if (rc == 0) {
/* Driver will handle this itself */
@@ -2491,22 +2492,25 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
u64 i = 0;
do {
- rtnl_lock();
+ if (has_rtnl_lock)
+ rtnl_lock();
netdev_lock_ops(dev);
rc = ops->set_phys_id(dev,
(i++ & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON);
netdev_unlock_ops(dev);
- rtnl_unlock();
+ if (has_rtnl_lock)
+ rtnl_unlock();
if (rc)
break;
schedule_timeout_interruptible(interval);
} while (!signal_pending(current) && (!id.data || i < count));
}
- rtnl_lock();
+ if (has_rtnl_lock)
+ rtnl_lock();
netdev_lock_ops(dev);
netdev_put(dev, &dev_tracker);
- busy = false;
+ dev->ethtool->phys_id_busy = false;
(void) ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE);
return rc;
@@ -3259,7 +3263,8 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr)
static int
dev_ethtool_locked(struct net *net, struct net_device *dev,
void __user *useraddr,
- u32 ethcmd, struct ethtool_devlink_compat *devlink_state)
+ u32 ethcmd, struct ethtool_devlink_compat *devlink_state,
+ bool has_rtnl_lock)
{
u32 sub_cmd;
int rc;
@@ -3315,6 +3320,8 @@ dev_ethtool_locked(struct net *net, struct net_device *dev,
return -EPERM;
}
+ netdev_ops_assert_locked(dev);
+
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);
@@ -3402,7 +3409,7 @@ dev_ethtool_locked(struct net *net, struct net_device *dev,
rc = ethtool_get_strings(dev, useraddr);
break;
case ETHTOOL_PHYS_ID:
- rc = ethtool_phys_id(dev, useraddr);
+ rc = ethtool_phys_id(dev, useraddr, has_rtnl_lock);
break;
case ETHTOOL_GSTATS:
rc = ethtool_get_stats(dev, useraddr);
@@ -3549,8 +3556,12 @@ dev_ethtool_locked(struct net *net, struct net_device *dev,
if (dev->ethtool_ops->complete)
dev->ethtool_ops->complete(dev);
- if (old_features != dev->features)
- netdev_features_change(dev);
+ if (old_features != dev->features) {
+ if (has_rtnl_lock)
+ netdev_features_change(dev);
+ else
+ netdev_WARN(dev, "unexpected device features change with ethtool cmd %u", ethcmd);
+ }
out:
if (dev->dev.parent)
pm_runtime_put(dev->dev.parent);
@@ -3558,25 +3569,60 @@ dev_ethtool_locked(struct net *net, struct net_device *dev,
return rc;
}
+/* Commands that may toggle dev->features in net/ethtool/ioctl.c and so
+ * call into __netdev_update_features(), which still requires rtnl_lock.
+ * Driver-decided SET commands that may chain into rtnl-only helpers are
+ * covered by ethtool_ioctl_needs_rtnl()/ETHTOOL_OP_NEEDS_RTNL_*.
+ */
+static bool ethtool_cmd_changes_features(u32 ethcmd)
+{
+ switch (ethcmd) {
+ case ETHTOOL_SFEATURES:
+ case ETHTOOL_SFLAGS:
+ case ETHTOOL_STXCSUM:
+ case ETHTOOL_SRXCSUM:
+ case ETHTOOL_SSG:
+ case ETHTOOL_STSO:
+ case ETHTOOL_SGSO:
+ case ETHTOOL_SGRO:
+ return true;
+ }
+ return false;
+}
+
static int
__dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
u32 ethcmd, struct ethtool_devlink_compat *devlink_state)
{
+ netdevice_tracker dev_tracker;
struct net_device *dev;
+ bool need_rtnl;
int rc;
- rtnl_lock();
- dev = __dev_get_by_name(net, ifr->ifr_name);
- if (!dev) {
+ dev = netdev_get_by_name(net, ifr->ifr_name, &dev_tracker, GFP_KERNEL);
+ if (!dev)
+ return -ENODEV;
+
+ need_rtnl = !netdev_need_ops_lock(dev) ||
+ ethtool_cmd_changes_features(ethcmd) ||
+ ethtool_ioctl_needs_rtnl(dev, ethcmd);
+ if (need_rtnl)
+ rtnl_lock();
+ netdev_lock_ops(dev);
+ if (dev->reg_state > NETREG_REGISTERED ||
+ dev->moving_ns || !net_eq(dev_net(dev), net)) {
rc = -ENODEV;
- goto exit_rtnl_unlock;
+ goto exit_ops_unlock;
}
- netdev_lock_ops(dev);
- rc = dev_ethtool_locked(net, dev, useraddr, ethcmd, devlink_state);
+ rc = dev_ethtool_locked(net, dev, useraddr, ethcmd, devlink_state,
+ need_rtnl);
+
+exit_ops_unlock:
netdev_unlock_ops(dev);
-exit_rtnl_unlock:
- rtnl_unlock();
+ if (need_rtnl)
+ rtnl_unlock();
+ netdev_put(dev, &dev_tracker);
return rc;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [PATCH net-next 14/14] docs: net: ethtool: document ops-locked drivers and op_needs_rtnl
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (12 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 13/14] net: ethtool: optionally skip rtnl_lock on IOCTL path Jakub Kicinski
@ 2026-05-28 23:16 ` Jakub Kicinski
2026-05-29 7:41 ` [syzbot ci] Re: net: ethtool: let ops locked drivers run without rtnl_lock syzbot ci
14 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-05-28 23:16 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, michael.chan,
joshwash, tariqt, haiyangz, linux, maxime.chevallier, willemb,
ernis, sdf.kernel, kory.maincent, danieller, idosch,
Jakub Kicinski
Catch up various bits of documentation after the locking changes.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
Documentation/networking/netdev-features.rst | 7 +++++++
Documentation/networking/netdevices.rst | 17 ++++++++++-------
include/linux/ethtool.h | 10 ++++++++--
3 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/Documentation/networking/netdev-features.rst b/Documentation/networking/netdev-features.rst
index 6293d47e5b09..f9e1f3921f53 100644
--- a/Documentation/networking/netdev-features.rst
+++ b/Documentation/networking/netdev-features.rst
@@ -76,6 +76,13 @@ netdev instance lock, that lock must be held as well. This should not be
done from ndo_*_features callbacks. netdev->features should not be modified
by driver except by means of ndo_fix_features callback.
+For "ops locked" drivers (see Documentation/networking/netdevices.rst),
+ethtool callbacks that may end up invoking netdev_update_features() must
+opt back into rtnl_lock by setting the matching ETHTOOL_OP_NEEDS_RTNL_*
+bit in ``ethtool_ops::op_needs_rtnl``. The ethtool core then keeps
+rtnl_lock held across those SET callbacks so the contract above still
+holds.
+
ndo_features_check is called for each skb before that skb is passed to
ndo_start_xmit. Driver may perform any non-trivial checks (e.g. exact
header geometry / length) and withdraw features like HW_CSUM or TSO,
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index 60492d4df2ee..30a4219041d5 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -351,10 +351,6 @@ virtual and the physical device. To prevent deadlocks, the virtual device's
lock must always be acquired before the physical device's (see
``netdev_nl_queue_create_doit``).
-In the future, there will be an option for individual
-drivers to opt out of using ``rtnl_lock`` and instead perform their control
-operations directly under the netdev instance lock.
-
Device drivers are encouraged to rely on the instance lock where possible.
For the (mostly software) drivers that need to interact with the core stack,
@@ -375,9 +371,16 @@ the instance lock.
struct ethtool_ops
------------------
-Similarly to ``ndos`` the instance lock is only held for select drivers.
-For "ops locked" drivers all ethtool ops without exceptions should
-be called under the instance lock.
+For non-"ops locked" drivers ethtool_ops are executed under ``rtnl_lock``.
+
+For "ops locked" drivers ``ethtool_ops``, unlike ``ndos``, run under
+the instance lock **only**. Driver may request that ``rtnl_lock``
+is held around specific operations (both SET and GET) by setting
+appropriate bits in ``ethtool_ops::op_needs_rtnl`` (if necessary
+``ETHTOOL_OP_NEEDS_RTNL_*`` bit doesn't exist just add it). Commonly
+used core helpers which force drivers to selectively opt-in to
+``rtnl_lock`` protection include ``netdev_update_features()``,
+``netif_set_real_num_*_queues()``, and phylink helpers.
struct netdev_stat_ops
----------------------
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 35ee57a0e5fa..b62028eaf28e 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -1177,8 +1177,14 @@ struct kernel_ethtool_ts_info {
* @get_mm_stats: Query the 802.3 MAC Merge layer statistics.
*
* All operations are optional (i.e. the function pointer may be set
- * to %NULL) and callers must take this into account. Callers must
- * hold the RTNL lock or netdev instance lock (see @op_needs_rtnl).
+ * to %NULL) and callers must take this into account.
+ *
+ * For traditional drivers callers hold ``rtnl_lock`` across the call.
+ * For "ops locked" drivers (see Documentation/networking/netdevices.rst)
+ * callers instead hold the netdev instance lock (``netdev_lock_ops``);
+ * ``rtnl_lock`` is additionally held only for callbacks for which
+ * the driver opts in via the matching ``ETHTOOL_OP_NEEDS_RTNL_*`` bit
+ * in @op_needs_rtnl.
*
* See the structures used by these operations for further documentation.
* Note that for all operations using a structure ending with a zero-
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [syzbot ci] Re: net: ethtool: let ops locked drivers run without rtnl_lock
2026-05-28 23:16 [PATCH net-next 00/14] net: ethtool: let ops locked drivers run without rtnl_lock Jakub Kicinski
` (13 preceding siblings ...)
2026-05-28 23:16 ` [PATCH net-next 14/14] docs: net: ethtool: document ops-locked drivers and op_needs_rtnl Jakub Kicinski
@ 2026-05-29 7:41 ` syzbot ci
14 siblings, 0 replies; 19+ messages in thread
From: syzbot ci @ 2026-05-29 7:41 UTC (permalink / raw)
To: andrew, danieller, davem, edumazet, ernis, haiyangz, horms,
idosch, joshwash, kory.maincent, kuba, linux, maxime.chevallier,
michael.chan, netdev, pabeni, sdf.kernel, tariqt, willemb
Cc: syzbot, syzkaller-bugs
syzbot ci has tested the following series
[v1] net: ethtool: let ops locked drivers run without rtnl_lock
https://lore.kernel.org/all/20260528231637.251822-1-kuba@kernel.org
* [PATCH net-next 01/14] net: ethtool: cmis_cdb: hold instance lock for ops locked devices
* [PATCH net-next 02/14] net: ethtool: make sure __ethtool_get_link_ksettings() is ops-locked
* [PATCH net-next 03/14] net: ethtool: serialize broadcast notification sequence allocation
* [PATCH net-next 04/14] net: ethtool: relax ethnl_req_get_phydev() locking assertion
* [PATCH net-next 05/14] net: ethtool: make dev->hwprov ops-protected
* [PATCH net-next 06/14] net: ethtool: optionally skip rtnl_lock on Netlink path for GET ops
* [PATCH net-next 07/14] net: ethtool: optionally skip rtnl_lock on Netlink path for SET ops
* [PATCH net-next 08/14] net: ethtool: optionally skip rtnl_lock in cable test handlers
* [PATCH net-next 09/14] net: ethtool: optionally skip rtnl_lock in ethnl_tsinfo_dumpit()
* [PATCH net-next 10/14] net: ethtool: optionally skip rtnl_lock in ethnl_act_module_fw_flash()
* [PATCH net-next 11/14] net: ethtool: optionally skip rtnl_lock in RSS context handlers
* [PATCH net-next 12/14] net: ethtool: ioctl: concentrate the locking
* [PATCH net-next 13/14] net: ethtool: optionally skip rtnl_lock on IOCTL path
* [PATCH net-next 14/14] docs: net: ethtool: document ops-locked drivers and op_needs_rtnl
and found the following issue:
possible deadlock in __ethtool_get_link_ksettings
Full report is available here:
https://ci.syzbot.org/series/ada8852f-9efd-4b85-87aa-2f5d8fe16040
***
possible deadlock in __ethtool_get_link_ksettings
tree: net-next
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/netdev/net-next.git
base: 9a82e387e27a4422a0d2d9d644180b7bd913e85a
arch: amd64
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config: https://ci.syzbot.org/builds/2968208d-6483-4d38-be45-de68d3b94775/config
syz repro: https://ci.syzbot.org/findings/d8670aa1-9241-49be-8108-cdbed56dd4a5/syz_repro
dummy0: entered promiscuous mode
bridge0: port 3(dummy0) entered blocking state
bridge0: port 3(dummy0) entered forwarding state
============================================
WARNING: possible recursive locking detected
syzkaller #0 Not tainted
--------------------------------------------
syz.1.28/5852 is trying to acquire lock:
ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: netdev_lock include/linux/netdevice.h:2833 [inline]
ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: netdev_lock_ops include/net/netdev_lock.h:42 [inline]
ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: __ethtool_get_link_ksettings+0x12b/0x290 net/ethtool/ioctl.c:462
but task is already holding lock:
ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: netdev_lock include/linux/netdevice.h:2833 [inline]
ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: netdev_lock_ops include/net/netdev_lock.h:42 [inline]
ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: do_setlink+0x3d2/0x45a0 net/core/rtnetlink.c:3117
and the lock comparison function returns 0:
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0
----
lock(&dev_instance_lock_key#3);
lock(&dev_instance_lock_key#3);
*** DEADLOCK ***
May be due to missing lock nesting notation
2 locks held by syz.1.28/5852:
#0: ffffffff8fdd2d80 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_lock net/core/rtnetlink.c:80 [inline]
#0: ffffffff8fdd2d80 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_nets_lock net/core/rtnetlink.c:341 [inline]
#0: ffffffff8fdd2d80 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_setlink+0x6bb/0xb40 net/core/rtnetlink.c:3527
#1: ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: netdev_lock include/linux/netdevice.h:2833 [inline]
#1: ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: netdev_lock_ops include/net/netdev_lock.h:42 [inline]
#1: ffff888115f78dc8 (&dev_instance_lock_key#3){+.+.}-{4:4}, at: do_setlink+0x3d2/0x45a0 net/core/rtnetlink.c:3117
stack backtrace:
CPU: 0 UID: 0 PID: 5852 Comm: syz.1.28 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_deadlock_bug+0x279/0x290 kernel/locking/lockdep.c:3041
check_deadlock kernel/locking/lockdep.c:3093 [inline]
validate_chain kernel/locking/lockdep.c:3895 [inline]
__lock_acquire+0x253f/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x1a3/0x1550 kernel/locking/mutex.c:820
netdev_lock include/linux/netdevice.h:2833 [inline]
netdev_lock_ops include/net/netdev_lock.h:42 [inline]
__ethtool_get_link_ksettings+0x12b/0x290 net/ethtool/ioctl.c:462
port_cost+0xc0/0x3a0 net/bridge/br_if.c:39
br_port_carrier_check+0x12d/0x3f0 net/bridge/br_if.c:80
br_device_event+0x65c/0x970 net/bridge/br.c:101
notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
__dev_notify_flags+0x248/0x310 net/core/dev.c:9804
netif_change_flags+0xe8/0x1a0 net/core/dev.c:9819
do_setlink+0xfa5/0x45a0 net/core/rtnetlink.c:3207
rtnl_setlink+0x792/0xb40 net/core/rtnetlink.c:3537
rtnetlink_rcv_msg+0x7d5/0xbe0 net/core/rtnetlink.c:7062
netlink_rcv_skb+0x232/0x4b0 net/netlink/af_netlink.c:2556
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x75c/0x8e0 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec net/socket.c:787 [inline]
__sock_sendmsg net/socket.c:802 [inline]
____sys_sendmsg+0x972/0x9f0 net/socket.c:2698
___sys_sendmsg+0x2a5/0x360 net/socket.c:2752
__sys_sendmsg net/socket.c:2784 [inline]
__do_sys_sendmsg net/socket.c:2789 [inline]
__se_sys_sendmsg net/socket.c:2787 [inline]
__x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2787
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0x560 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f087ad9ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f087bc25028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007f087b015fa0 RCX: 00007f087ad9ce59
RDX: 0000000000000000 RSI: 0000200000000040 RDI: 0000000000000004
RBP: 00007f087ae32d6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f087b016038 R14: 00007f087b015fa0 R15: 00007ffcefdf0348
</TASK>
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
To test a patch for this bug, please reply with `#syz test`
(should be on a separate line).
The patch should be attached to the email.
Note: arguments like custom git repos and branches are not supported.
^ permalink raw reply [flat|nested] 19+ messages in thread