Skip to content

Commit

Permalink
net: ethtool: try to protect all callback with netdev instance lock
Browse files Browse the repository at this point in the history
Protect all ethtool callbacks and PHY related state with the netdev
instance lock, for drivers which want / need to have their ops
instance-locked. Basically take the lock everywhere we take rtnl_lock.
It was tempting to take the lock in ethnl_ops_begin(), but turns
out we actually nest those calls (when generating notifications).

Tested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Cc: Saeed Mahameed <saeed@kernel.org>
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
Link: https://patch.msgid.link/20250305163732.2766420-11-sdf@fomichev.me
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Jakub Kicinski committed Mar 6, 2025
1 parent 97246d6 commit 2bcf477
Showing 11 changed files with 82 additions and 26 deletions.
2 changes: 0 additions & 2 deletions drivers/net/netdevsim/ethtool.c
Original file line number Diff line number Diff line change
@@ -107,10 +107,8 @@ nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch)
struct netdevsim *ns = netdev_priv(dev);
int err;

netdev_lock(dev);
err = netif_set_real_num_queues(dev, ch->combined_count,
ch->combined_count);
netdev_unlock(dev);
if (err)
return err;

16 changes: 15 additions & 1 deletion net/dsa/conduit.c
Original file line number Diff line number Diff line change
@@ -26,7 +26,9 @@ static int dsa_conduit_get_regs_len(struct net_device *dev)
int len;

if (ops->get_regs_len) {
netdev_lock_ops(dev);
len = ops->get_regs_len(dev);
netdev_unlock_ops(dev);
if (len < 0)
return len;
ret += len;
@@ -57,11 +59,15 @@ static void dsa_conduit_get_regs(struct net_device *dev,
int len;

if (ops->get_regs_len && ops->get_regs) {
netdev_lock_ops(dev);
len = ops->get_regs_len(dev);
if (len < 0)
if (len < 0) {
netdev_unlock_ops(dev);
return;
}
regs->len = len;
ops->get_regs(dev, regs, data);
netdev_unlock_ops(dev);
data += regs->len;
}

@@ -91,8 +97,10 @@ static void dsa_conduit_get_ethtool_stats(struct net_device *dev,
int count = 0;

if (ops->get_sset_count && ops->get_ethtool_stats) {
netdev_lock_ops(dev);
count = ops->get_sset_count(dev, ETH_SS_STATS);
ops->get_ethtool_stats(dev, stats, data);
netdev_unlock_ops(dev);
}

if (ds->ops->get_ethtool_stats)
@@ -114,8 +122,10 @@ static void dsa_conduit_get_ethtool_phy_stats(struct net_device *dev,
if (count >= 0)
phy_ethtool_get_stats(dev->phydev, stats, data);
} else if (ops->get_sset_count && ops->get_ethtool_phy_stats) {
netdev_lock_ops(dev);
count = ops->get_sset_count(dev, ETH_SS_PHY_STATS);
ops->get_ethtool_phy_stats(dev, stats, data);
netdev_unlock_ops(dev);
}

if (count < 0)
@@ -132,11 +142,13 @@ static int dsa_conduit_get_sset_count(struct net_device *dev, int sset)
struct dsa_switch *ds = cpu_dp->ds;
int count = 0;

netdev_lock_ops(dev);
if (sset == ETH_SS_PHY_STATS && dev->phydev &&
!ops->get_ethtool_phy_stats)
count = phy_ethtool_get_sset_count(dev->phydev);
else if (ops->get_sset_count)
count = ops->get_sset_count(dev, sset);
netdev_unlock_ops(dev);

if (count < 0)
count = 0;
@@ -163,6 +175,7 @@ static void dsa_conduit_get_strings(struct net_device *dev, uint32_t stringset,
/* We do not want to be NULL-terminated, since this is a prefix */
pfx[sizeof(pfx) - 1] = '_';

netdev_lock_ops(dev);
if (stringset == ETH_SS_PHY_STATS && dev->phydev &&
!ops->get_ethtool_phy_stats) {
mcount = phy_ethtool_get_sset_count(dev->phydev);
@@ -176,6 +189,7 @@ static void dsa_conduit_get_strings(struct net_device *dev, uint32_t stringset,
mcount = 0;
ops->get_strings(dev, stringset, data);
}
netdev_unlock_ops(dev);

if (ds->ops->get_strings) {
ndata = data + mcount * len;
20 changes: 12 additions & 8 deletions net/ethtool/cabletest.c
Original file line number Diff line number Diff line change
@@ -72,23 +72,24 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;

rtnl_lock();
netdev_lock_ops(dev);
phydev = ethnl_req_get_phydev(&req_info,
tb[ETHTOOL_A_CABLE_TEST_HEADER],
info->extack);
if (IS_ERR_OR_NULL(phydev)) {
ret = -EOPNOTSUPP;
goto out_rtnl;
goto out_unlock;
}

ops = ethtool_phy_ops;
if (!ops || !ops->start_cable_test) {
ret = -EOPNOTSUPP;
goto out_rtnl;
goto out_unlock;
}

ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
goto out_unlock;

ret = ops->start_cable_test(phydev, info->extack);

@@ -97,7 +98,8 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
if (!ret)
ethnl_cable_test_started(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);

out_rtnl:
out_unlock:
netdev_unlock_ops(dev);
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info);
return ret;
@@ -339,23 +341,24 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
goto out_dev_put;

rtnl_lock();
netdev_lock_ops(dev);
phydev = ethnl_req_get_phydev(&req_info,
tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
info->extack);
if (IS_ERR_OR_NULL(phydev)) {
ret = -EOPNOTSUPP;
goto out_rtnl;
goto out_unlock;
}

ops = ethtool_phy_ops;
if (!ops || !ops->start_cable_test_tdr) {
ret = -EOPNOTSUPP;
goto out_rtnl;
goto out_unlock;
}

ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
goto out_unlock;

ret = ops->start_cable_test_tdr(phydev, info->extack, &cfg);

@@ -365,7 +368,8 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
ethnl_cable_test_started(phydev,
ETHTOOL_MSG_CABLE_TEST_TDR_NTF);

out_rtnl:
out_unlock:
netdev_unlock_ops(dev);
rtnl_unlock();
out_dev_put:
ethnl_parse_header_dev_put(&req_info);
7 changes: 6 additions & 1 deletion net/ethtool/cmis_fw_update.c
Original file line number Diff line number Diff line change
@@ -418,8 +418,13 @@ 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;

return dev->ethtool_ops->reset(dev, &reset_data);
netdev_lock_ops(dev);
ret = dev->ethtool_ops->reset(dev, &reset_data);
netdev_unlock_ops(dev);

return ret;
}

void
6 changes: 4 additions & 2 deletions net/ethtool/features.c
Original file line number Diff line number Diff line change
@@ -234,9 +234,10 @@ int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;

rtnl_lock();
netdev_lock_ops(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
goto out_unlock;
ethnl_features_to_bitmap(old_active, dev->features);
ethnl_features_to_bitmap(old_wanted, dev->wanted_features);
ret = ethnl_parse_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
@@ -286,7 +287,8 @@ int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)

out_ops:
ethnl_ops_complete(dev);
out_rtnl:
out_unlock:
netdev_unlock_ops(dev);
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info);
return ret;
6 changes: 6 additions & 0 deletions net/ethtool/ioctl.c
Original file line number Diff line number Diff line change
@@ -2317,6 +2317,7 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
*/
busy = true;
netdev_hold(dev, &dev_tracker, GFP_KERNEL);
netdev_unlock_ops(dev);
rtnl_unlock();

if (rc == 0) {
@@ -2331,8 +2332,10 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)

do {
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 (rc)
break;
@@ -2341,6 +2344,7 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
}

rtnl_lock();
netdev_lock_ops(dev);
netdev_put(dev, &dev_tracker);
busy = false;

@@ -3140,6 +3144,7 @@ __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);

@@ -3373,6 +3378,7 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
out:
if (dev->dev.parent)
pm_runtime_put(dev->dev.parent);
netdev_unlock_ops(dev);

return rc;
}
8 changes: 5 additions & 3 deletions net/ethtool/module.c
Original file line number Diff line number Diff line change
@@ -419,19 +419,21 @@ int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;

rtnl_lock();
netdev_lock_ops(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
goto out_unlock;

ret = ethnl_module_fw_flash_validate(dev, info->extack);
if (ret < 0)
goto out_rtnl;
goto out_unlock;

ret = module_flash_fw(dev, tb, skb, info);

ethnl_ops_complete(dev);

out_rtnl:
out_unlock:
netdev_unlock_ops(dev);
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info);
return ret;
12 changes: 12 additions & 0 deletions net/ethtool/netlink.c
Original file line number Diff line number Diff line change
@@ -90,6 +90,8 @@ int ethnl_ops_begin(struct net_device *dev)
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);

netdev_ops_assert_locked(dev);

if (!netif_device_present(dev) ||
dev->reg_state >= NETREG_UNREGISTERING) {
ret = -ENODEV;
@@ -490,7 +492,11 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
ethnl_init_reply_data(reply_data, ops, req_info->dev);

rtnl_lock();
if (req_info->dev)
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 (ret < 0)
goto err_cleanup;
@@ -548,7 +554,9 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,

ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
rtnl_lock();
netdev_lock_ops(ctx->req_info->dev);
ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info);
netdev_unlock_ops(ctx->req_info->dev);
rtnl_unlock();
if (ret < 0)
goto out;
@@ -693,6 +701,7 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;

rtnl_lock();
netdev_lock_ops(dev);
dev->cfg_pending = kmemdup(dev->cfg, sizeof(*dev->cfg),
GFP_KERNEL_ACCOUNT);
if (!dev->cfg_pending) {
@@ -720,6 +729,7 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
kfree(dev->cfg_pending);
out_tie_cfg:
dev->cfg_pending = dev->cfg;
netdev_unlock_ops(dev);
rtnl_unlock();
out_dev:
ethnl_parse_header_dev_put(&req_info);
@@ -777,6 +787,8 @@ static void ethnl_default_notify(struct net_device *dev, unsigned int cmd,
req_info->dev = dev;
req_info->flags |= ETHTOOL_FLAG_COMPACT_BITSETS;

netdev_ops_assert_locked(dev);

ethnl_init_reply_data(reply_data, ops, dev);
ret = ops->prepare_data(req_info, reply_data, &info);
if (ret < 0)
20 changes: 14 additions & 6 deletions net/ethtool/phy.c
Original file line number Diff line number Diff line change
@@ -158,18 +158,19 @@ int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info)
return ret;

rtnl_lock();
netdev_lock_ops(req_info.base.dev);

ret = ethnl_phy_parse_request(&req_info.base, tb, info->extack);
if (ret < 0)
goto err_unlock_rtnl;
goto err_unlock;

/* No PHY, return early */
if (!req_info.pdn)
goto err_unlock_rtnl;
goto err_unlock;

ret = ethnl_phy_reply_size(&req_info.base, info->extack);
if (ret < 0)
goto err_unlock_rtnl;
goto err_unlock;
reply_len = ret + ethnl_reply_header_size();

rskb = ethnl_reply_init(reply_len, req_info.base.dev,
@@ -178,13 +179,14 @@ int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info)
info, &reply_payload);
if (!rskb) {
ret = -ENOMEM;
goto err_unlock_rtnl;
goto err_unlock;
}

ret = ethnl_phy_fill_reply(&req_info.base, rskb);
if (ret)
goto err_free_msg;

netdev_unlock_ops(req_info.base.dev);
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info.base);
genlmsg_end(rskb, reply_payload);
@@ -193,7 +195,8 @@ int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info)

err_free_msg:
nlmsg_free(rskb);
err_unlock_rtnl:
err_unlock:
netdev_unlock_ops(req_info.base.dev);
rtnl_unlock();
ethnl_parse_header_dev_put(&req_info.base);
return ret;
@@ -290,10 +293,15 @@ int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
rtnl_lock();

if (ctx->phy_req_info->base.dev) {
ret = ethnl_phy_dump_one_dev(skb, ctx->phy_req_info->base.dev, cb);
dev = ctx->phy_req_info->base.dev;
netdev_lock_ops(dev);
ret = ethnl_phy_dump_one_dev(skb, dev, cb);
netdev_unlock_ops(dev);
} else {
for_each_netdev_dump(net, dev, ctx->ifindex) {
netdev_lock_ops(dev);
ret = ethnl_phy_dump_one_dev(skb, dev, cb);
netdev_unlock_ops(dev);
if (ret)
break;

Loading

0 comments on commit 2bcf477

Please sign in to comment.