summaryrefslogtreecommitdiff
path: root/net/ethtool
diff options
context:
space:
mode:
Diffstat (limited to 'net/ethtool')
-rw-r--r--net/ethtool/bitset.c8
-rw-r--r--net/ethtool/cabletest.c12
-rw-r--r--net/ethtool/cmis.h4
-rw-r--r--net/ethtool/cmis_cdb.c12
-rw-r--r--net/ethtool/cmis_fw_update.c52
-rw-r--r--net/ethtool/coalesce.c6
-rw-r--r--net/ethtool/common.c4
-rw-r--r--net/ethtool/common.h87
-rw-r--r--net/ethtool/eeprom.c10
-rw-r--r--net/ethtool/ioctl.c151
-rw-r--r--net/ethtool/linkinfo.c4
-rw-r--r--net/ethtool/linkmodes.c4
-rw-r--r--net/ethtool/linkstate.c6
-rw-r--r--net/ethtool/mm.c5
-rw-r--r--net/ethtool/module.c49
-rw-r--r--net/ethtool/netlink.c76
-rw-r--r--net/ethtool/netlink.h44
-rw-r--r--net/ethtool/phy.c37
-rw-r--r--net/ethtool/pse-pd.c10
-rw-r--r--net/ethtool/rings.c4
-rw-r--r--net/ethtool/rss.c72
-rw-r--r--net/ethtool/strset.c2
-rw-r--r--net/ethtool/tsconfig.c41
-rw-r--r--net/ethtool/tsinfo.c51
24 files changed, 534 insertions, 217 deletions
diff --git a/net/ethtool/bitset.c b/net/ethtool/bitset.c
index 8bb98d3ea3db..a3a2cc6480a0 100644
--- a/net/ethtool/bitset.c
+++ b/net/ethtool/bitset.c
@@ -92,7 +92,7 @@ static bool ethnl_bitmap32_not_zero(const u32 *map, unsigned int start,
u32 mask;
if (end <= start)
- return true;
+ return false;
if (start % 32) {
mask = ethnl_upper_bits(start);
@@ -105,11 +105,11 @@ static bool ethnl_bitmap32_not_zero(const u32 *map, unsigned int start,
start_word++;
}
- if (!memchr_inv(map + start_word, '\0',
- (end_word - start_word) * sizeof(u32)))
+ if (memchr_inv(map + start_word, '\0',
+ (end_word - start_word) * sizeof(u32)))
return true;
if (end % 32 == 0)
- return true;
+ return false;
return map[end_word] & ethnl_lower_bits(end);
}
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;
diff --git a/net/ethtool/cmis.h b/net/ethtool/cmis.h
index 4a9a946cabf0..778783a0f23c 100644
--- a/net/ethtool/cmis.h
+++ b/net/ethtool/cmis.h
@@ -63,9 +63,9 @@ struct ethtool_cmis_cdb_request {
* struct ethtool_cmis_cdb_cmd_args - CDB commands execution arguments
* @req: CDB command fields as described in the CMIS standard.
* @max_duration: Maximum duration time for command completion in msec.
+ * @msleep_pre_rpl: Waiting time before checking reply in msec.
* @read_write_len_ext: Allowable additional number of byte octets to the LPL
* in a READ or a WRITE commands.
- * @msleep_pre_rpl: Waiting time before checking reply in msec.
* @rpl_exp_len: Expected reply length in bytes.
* @flags: Validation flags for CDB commands.
* @err_msg: Error message to be sent to user space.
@@ -73,8 +73,8 @@ struct ethtool_cmis_cdb_request {
struct ethtool_cmis_cdb_cmd_args {
struct ethtool_cmis_cdb_request req;
u16 max_duration;
+ u16 msleep_pre_rpl;
u8 read_write_len_ext;
- u8 msleep_pre_rpl;
u8 rpl_exp_len;
u8 flags;
char *err_msg;
diff --git a/net/ethtool/cmis_cdb.c b/net/ethtool/cmis_cdb.c
index 3670ca42dd40..a4e8f4b3fb75 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_ops(dev);
err = ops->set_module_eeprom_by_page(dev, &page_data, &extack);
if (err < 0) {
if (extack._msg)
@@ -513,8 +515,13 @@ static int cmis_cdb_process_reply(struct net_device *dev,
}
rpl = (struct ethtool_cmis_cdb_rpl *)page_data->data;
- if ((args->rpl_exp_len > rpl->hdr.rpl_len + rpl_hdr_len) ||
- !rpl->hdr.rpl_chk_code) {
+ if (rpl->hdr.rpl_len != args->rpl_exp_len) {
+ netdev_warn(dev, "CDB reply length mismatch, expected %u got %u\n",
+ args->rpl_exp_len, rpl->hdr.rpl_len);
+ err = -EIO;
+ goto out;
+ }
+ if (!rpl->hdr.rpl_chk_code) {
err = -EIO;
goto out;
}
@@ -541,6 +548,7 @@ __ethtool_cmis_cdb_execute_cmd(struct net_device *dev,
if (!page_data->data)
return -ENOMEM;
+ netdev_assert_locked_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 df5f344209c4..dff83807e975 100644
--- a/net/ethtool/cmis_fw_update.c
+++ b/net/ethtool/cmis_fw_update.c
@@ -44,6 +44,20 @@ enum cmis_cdb_fw_write_mechanism {
CMIS_CDB_FW_WRITE_MECHANISM_BOTH = 0x11,
};
+/* See section 9.7.2 "CMD 0101h: Start Firmware Download" in CMIS standard
+ * revision 5.2.
+ * struct cmis_cdb_start_fw_download_pl is a structured layout of the
+ * flat array, ethtool_cmis_cdb_request::payload.
+ */
+struct cmis_cdb_start_fw_download_pl {
+ __struct_group(cmis_cdb_start_fw_download_pl_h, head, /* no attrs */,
+ __be32 image_size;
+ __be32 resv1;
+ );
+ u8 vendor_data[ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH -
+ sizeof(struct cmis_cdb_start_fw_download_pl_h)];
+};
+
static int
cmis_fw_update_fw_mng_features_get(struct ethtool_cmis_cdb *cdb,
struct net_device *dev,
@@ -86,6 +100,14 @@ cmis_fw_update_fw_mng_features_get(struct ethtool_cmis_cdb *cdb,
*/
cdb->read_write_len_ext = rpl->read_write_len_ext;
fw_mng->start_cmd_payload_size = rpl->start_cmd_payload_size;
+ if (fw_mng->start_cmd_payload_size >
+ sizeof_field(struct cmis_cdb_start_fw_download_pl, vendor_data)) {
+ ethnl_module_fw_flash_ntf_err(dev, ntf_params,
+ "Start cmd payload size exceeds max LPL payload",
+ NULL);
+ return -EINVAL;
+ }
+
fw_mng->write_mechanism =
rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_LPL ?
CMIS_CDB_FW_WRITE_MECHANISM_LPL :
@@ -97,20 +119,6 @@ cmis_fw_update_fw_mng_features_get(struct ethtool_cmis_cdb *cdb,
return 0;
}
-/* See section 9.7.2 "CMD 0101h: Start Firmware Download" in CMIS standard
- * revision 5.2.
- * struct cmis_cdb_start_fw_download_pl is a structured layout of the
- * flat array, ethtool_cmis_cdb_request::payload.
- */
-struct cmis_cdb_start_fw_download_pl {
- __struct_group(cmis_cdb_start_fw_download_pl_h, head, /* no attrs */,
- __be32 image_size;
- __be32 resv1;
- );
- u8 vendor_data[ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH -
- sizeof(struct cmis_cdb_start_fw_download_pl_h)];
-};
-
static int
cmis_fw_update_start_download(struct ethtool_cmis_cdb *cdb,
struct ethtool_cmis_fw_update_params *fw_update,
@@ -122,6 +130,14 @@ cmis_fw_update_start_download(struct ethtool_cmis_cdb *cdb,
u8 lpl_len;
int err;
+ if (fw_update->fw->size < vendor_data_size) {
+ ethnl_module_fw_flash_ntf_err(fw_update->dev,
+ &fw_update->ntf_params,
+ "Firmware image too small for module's start payload",
+ NULL);
+ return -EINVAL;
+ }
+
pl.image_size = cpu_to_be32(fw_update->fw->size);
memcpy(pl.vendor_data, fw_update->fw->data, vendor_data_size);
@@ -419,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_ops(dev);
+ return dev->ethtool_ops->reset(dev, &reset_data);
}
void
diff --git a/net/ethtool/coalesce.c b/net/ethtool/coalesce.c
index 1e2c5c7048a8..e73fc3e5a02b 100644
--- a/net/ethtool/coalesce.c
+++ b/net/ethtool/coalesce.c
@@ -472,6 +472,12 @@ static int ethnl_update_profile(struct net_device *dev,
nla_for_each_nested_type(nest, ETHTOOL_A_PROFILE_IRQ_MODERATION,
nests, rem) {
+ if (i >= NET_DIM_PARAMS_NUM_PROFILES) {
+ NL_SET_BAD_ATTR(extack, nest);
+ ret = -E2BIG;
+ goto err_out;
+ }
+
ret = nla_parse_nested(tb, len_irq_moder - 1, nest,
coalesce_irq_moderation_policy,
extack);
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 84ec88dee05c..23db40618fed 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -67,7 +67,6 @@ const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
[NETIF_F_HW_ESP_BIT] = "esp-hw-offload",
[NETIF_F_HW_ESP_TX_CSUM_BIT] = "esp-tx-csum-hw-offload",
[NETIF_F_RX_UDP_TUNNEL_PORT_BIT] = "rx-udp_tunnel-port-offload",
- [NETIF_F_HW_TLS_RECORD_BIT] = "tls-hw-record",
[NETIF_F_HW_TLS_TX_BIT] = "tls-hw-tx-offload",
[NETIF_F_HW_TLS_RX_BIT] = "tls-hw-rx-offload",
[NETIF_F_GRO_FRAGLIST_BIT] = "rx-gro-list",
@@ -101,6 +100,9 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
[ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift",
[ETHTOOL_PHY_FAST_LINK_DOWN] = "phy-fast-link-down",
[ETHTOOL_PHY_EDPD] = "phy-energy-detect-power-down",
+ [ETHTOOL_PHY_SHORT_CABLE_PRESET] = "phy-short-cable-preset",
+ [ETHTOOL_PHY_LPF_BW] = "phy-lpf-bandwidth",
+ [ETHTOOL_PHY_DSP_EQ_INIT_VALUE] = "phy-dsp-eq-init-value",
};
#define __LINK_MODE_NAME(speed, type, duplex) \
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 1609cf4e53eb..4e5356e26f40 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -80,6 +80,93 @@ 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_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;
+ case ETHTOOL_MSG_LINKSTATE_GET:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK;
+ case ETHTOOL_MSG_TSCONFIG_GET:
+ case ETHTOOL_MSG_TSCONFIG_SET:
+ /* tsconfig calls ndos (ndo_hwtstamp_set/get), not ethtool ops.
+ * Also, there is no corresponding ethtool ioctl, therefore
+ * these cases are Netlink-only.
+ */
+ return true;
+ }
+ 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:
+ 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;
+ case ETHTOOL_GLINK:
+ return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK;
+ }
+ 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/net/ethtool/eeprom.c b/net/ethtool/eeprom.c
index a557e3996c85..0b8cfeddb014 100644
--- a/net/ethtool/eeprom.c
+++ b/net/ethtool/eeprom.c
@@ -44,6 +44,9 @@ static int fallback_set_params(struct eeprom_req_info *request,
if (offset >= modinfo->eeprom_len)
return -EINVAL;
+ if (length > modinfo->eeprom_len - offset)
+ return -EINVAL;
+
eeprom->cmd = ETHTOOL_GMODULEEEPROM;
eeprom->len = length;
eeprom->offset = offset;
@@ -69,7 +72,7 @@ static int eeprom_fallback(struct eeprom_req_info *request,
if (err < 0)
return err;
- data = kmalloc(eeprom.len, GFP_KERNEL);
+ data = kzalloc(eeprom.len, GFP_KERNEL);
if (!data)
return -ENOMEM;
err = ethtool_get_module_eeprom_call(dev, &eeprom, data);
@@ -141,12 +144,11 @@ static int eeprom_prepare_data(const struct ethnl_req_info *req_base,
return 0;
err_ops:
+ if (ret == -EOPNOTSUPP)
+ ret = eeprom_fallback(request, reply);
ethnl_ops_complete(dev);
err_free:
kfree(page_data.data);
-
- if (ret == -EOPNOTSUPP)
- return eeprom_fallback(request, reply);
return ret;
}
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index bd97f9b9bf18..4b0bc503f930 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 netif_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *link_ksettings)
{
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
if (!dev->ethtool_ops->get_link_ksettings)
return -EOPNOTSUPP;
@@ -450,6 +450,21 @@ 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);
}
+EXPORT_SYMBOL(netif_get_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 = netif_get_link_ksettings(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
@@ -529,7 +544,7 @@ static int ethtool_get_link_ksettings(struct net_device *dev,
int err = 0;
struct ethtool_link_ksettings link_ksettings;
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
if (!dev->ethtool_ops->get_link_ksettings)
return -EOPNOTSUPP;
@@ -586,7 +601,7 @@ static int ethtool_set_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings link_ksettings = {};
int err;
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
if (!dev->ethtool_ops->set_link_ksettings)
return -EOPNOTSUPP;
@@ -660,7 +675,7 @@ static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
struct ethtool_cmd cmd;
int err;
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
if (!dev->ethtool_ops->get_link_ksettings)
return -EOPNOTSUPP;
@@ -696,7 +711,7 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
struct ethtool_cmd cmd;
int ret;
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
return -EFAULT;
@@ -2437,10 +2452,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;
@@ -2448,7 +2463,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)))
@@ -2458,13 +2473,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 */
@@ -2477,22 +2493,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;
@@ -3114,6 +3133,7 @@ static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna)
switch (tuna->id) {
case ETHTOOL_PHY_DOWNSHIFT:
case ETHTOOL_PHY_FAST_LINK_DOWN:
+ case ETHTOOL_PHY_SHORT_CABLE_PRESET:
if (tuna->len != sizeof(u8) ||
tuna->type_id != ETHTOOL_TUNABLE_U8)
return -EINVAL;
@@ -3123,6 +3143,12 @@ static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna)
tuna->type_id != ETHTOOL_TUNABLE_U16)
return -EINVAL;
break;
+ case ETHTOOL_PHY_LPF_BW:
+ case ETHTOOL_PHY_DSP_EQ_INIT_VALUE:
+ if (tuna->len != sizeof(u32) ||
+ tuna->type_id != ETHTOOL_TUNABLE_U32)
+ return -EINVAL;
+ break;
default:
return -EINVAL;
}
@@ -3243,18 +3269,15 @@ 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,
+ bool has_rtnl_lock)
{
- 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;
@@ -3305,7 +3328,8 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
return -EPERM;
}
- netdev_lock_ops(dev);
+ netdev_assert_locked_ops_compat(dev);
+
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);
@@ -3393,7 +3417,7 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
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);
@@ -3540,12 +3564,81 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
if (dev->ethtool_ops->complete)
dev->ethtool_ops->complete(dev);
- if (old_features != dev->features)
- netdev_features_change(dev);
+ switch (ethcmd) {
+ case ETHTOOL_PHYS_ID:
+ /* Don't check features if operation drops the locks.
+ * Someone else may have changed features in parallel.
+ */
+ break;
+ default:
+ if (old_features != dev->features) {
+ if (has_rtnl_lock)
+ netdev_features_change(dev);
+ else
+ netdev_WARN(dev, "ethtool cmd %u changed features without rtnl_lock", ethcmd);
+ }
+ }
out:
if (dev->dev.parent)
pm_runtime_put(dev->dev.parent);
+
+ 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;
+
+ 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_ops_unlock;
+ }
+
+ rc = dev_ethtool_locked(net, dev, useraddr, ethcmd, devlink_state,
+ need_rtnl);
+
+exit_ops_unlock:
netdev_unlock_ops(dev);
+ if (need_rtnl)
+ rtnl_unlock();
+ netdev_put(dev, &dev_tracker);
return rc;
}
@@ -3573,9 +3666,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;
diff --git a/net/ethtool/linkinfo.c b/net/ethtool/linkinfo.c
index 244ff92e2ff9..d5b3dbc53c5f 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 = netif_get_link_ksettings(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 = netif_get_link_ksettings(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..a6d32f0d9fcc 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 = netif_get_link_ksettings(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 = netif_get_link_ksettings(dev, &ksettings);
if (ret < 0) {
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
return ret;
diff --git a/net/ethtool/linkstate.c b/net/ethtool/linkstate.c
index 8a5985fd7712..24569e92942c 100644
--- a/net/ethtool/linkstate.c
+++ b/net/ethtool/linkstate.c
@@ -106,10 +106,8 @@ static int linkstate_prepare_data(const struct ethnl_req_info *req_base,
phydev = ethnl_req_get_phydev(req_base, tb, ETHTOOL_A_LINKSTATE_HEADER,
info->extack);
- if (IS_ERR(phydev)) {
- ret = PTR_ERR(phydev);
- goto out;
- }
+ if (IS_ERR(phydev))
+ return PTR_ERR(phydev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
diff --git a/net/ethtool/mm.c b/net/ethtool/mm.c
index 29bbbc149375..2d10e2a1393f 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_assert_locked_ops_compat() 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/module.c b/net/ethtool/module.c
index cad2eb25b5a4..9cf670e089f2 100644
--- a/net/ethtool/module.c
+++ b/net/ethtool/module.c
@@ -120,12 +120,6 @@ ethnl_set_module_validate(struct ethnl_req_info *req_info,
if (!tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY])
return 0;
- if (req_info->dev->ethtool->module_fw_flash_in_progress) {
- NL_SET_ERR_MSG(info->extack,
- "Module firmware flashing is in progress");
- return -EBUSY;
- }
-
if (!ops->get_module_power_mode || !ops->set_module_power_mode) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY],
@@ -148,6 +142,12 @@ ethnl_set_module(struct ethnl_req_info *req_info, struct genl_info *info)
ops = dev->ethtool_ops;
+ if (dev->ethtool->module_fw_flash_in_progress) {
+ NL_SET_ERR_MSG(info->extack,
+ "Module firmware flashing is in progress");
+ return -EBUSY;
+ }
+
power_new.policy = nla_get_u8(tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY]);
ret = ops->get_module_power_mode(dev, &power, info->extack);
if (ret < 0)
@@ -221,14 +221,24 @@ static void module_flash_fw_work_list_del(struct list_head *list)
static void module_flash_fw_work(struct work_struct *work)
{
struct ethtool_module_fw_flash *module_fw;
+ struct net_device *dev;
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);
- module_fw->fw_update.dev->ethtool->module_fw_flash_in_progress = false;
- netdev_put(module_fw->fw_update.dev, &module_fw->dev_tracker);
+
+ rtnl_lock();
+ netdev_lock_ops(dev);
+ dev->ethtool->module_fw_flash_in_progress = false;
+ netdev_unlock_ops(dev);
+ rtnl_unlock();
+
+ netdev_put(dev, &module_fw->dev_tracker);
release_firmware(module_fw->fw_update.fw);
kfree(module_fw);
}
@@ -283,11 +293,9 @@ void ethnl_module_fw_flash_sock_destroy(struct ethnl_sock_priv *sk_priv)
spin_lock(&module_fw_flash_work_list_lock);
list_for_each_entry(work, &module_fw_flash_work_list, list) {
- if (work->fw_update.dev == sk_priv->dev &&
- work->fw_update.ntf_params.portid == sk_priv->portid) {
+ if (work->fw_update.ntf_params.portid == sk_priv->portid &&
+ dev_net(work->fw_update.dev) == sk_priv->net)
work->fw_update.ntf_params.closed_sock = true;
- break;
- }
}
spin_unlock(&module_fw_flash_work_list_lock);
}
@@ -319,14 +327,13 @@ module_flash_fw_schedule(struct net_device *dev, const char *file_name,
if (err < 0)
goto err_release_firmware;
- dev->ethtool->module_fw_flash_in_progress = true;
- netdev_hold(dev, &module_fw->dev_tracker, GFP_KERNEL);
fw_update->dev = dev;
fw_update->ntf_params.portid = info->snd_portid;
fw_update->ntf_params.seq = info->snd_seq;
fw_update->ntf_params.closed_sock = false;
- err = ethnl_sock_priv_set(skb, dev, fw_update->ntf_params.portid,
+ err = ethnl_sock_priv_set(skb, dev_net(dev),
+ fw_update->ntf_params.portid,
ETHTOOL_SOCK_TYPE_MODULE_FW_FLASH);
if (err < 0)
goto err_release_firmware;
@@ -335,6 +342,9 @@ module_flash_fw_schedule(struct net_device *dev, const char *file_name,
if (err < 0)
goto err_release_firmware;
+ dev->ethtool->module_fw_flash_in_progress = true;
+ netdev_hold(dev, &module_fw->dev_tracker, GFP_KERNEL);
+
schedule_work(&module_fw->work);
return 0;
@@ -419,23 +429,22 @@ 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;
ret = ethnl_module_fw_flash_validate(dev, info->extack);
if (ret < 0)
- goto out_unlock;
+ goto out_complete;
ret = module_flash_fw(dev, tb, skb, info);
+out_complete:
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;
}
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 5046023a30b1..1af395b54330 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -7,12 +7,19 @@
#include <linux/phy_link_topology.h>
#include <linux/pm_runtime.h>
+#include "common.h"
#include "module_fw.h"
#include "netlink.h"
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 | \
@@ -53,7 +60,7 @@ const struct nla_policy ethnl_header_policy_phy_stats[] = {
[ETHTOOL_A_HEADER_PHY_INDEX] = NLA_POLICY_MIN(NLA_U32, 1),
};
-int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
+int ethnl_sock_priv_set(struct sk_buff *skb, struct net *net, u32 portid,
enum ethnl_sock_type type)
{
struct ethnl_sock_priv *sk_priv;
@@ -62,7 +69,7 @@ int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
if (IS_ERR(sk_priv))
return PTR_ERR(sk_priv);
- sk_priv->dev = dev;
+ sk_priv->net = net;
sk_priv->portid = portid;
sk_priv->type = type;
@@ -82,12 +89,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;
@@ -98,7 +99,7 @@ int ethnl_ops_begin(struct net_device *dev)
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);
- netdev_ops_assert_locked(dev);
+ netdev_assert_locked_ops_compat(dev);
if (!netif_device_present(dev) ||
dev->reg_state >= NETREG_UNREGISTERING) {
@@ -226,11 +227,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;
@@ -329,8 +332,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, &ethtool_genl_family, 0,
- cmd);
+ return genlmsg_put(skb, 0, 0, &ethtool_genl_family, 0, cmd);
}
void *ethnl_unicast_put(struct sk_buff *skb, u32 portid, u32 seq, u8 cmd)
@@ -340,8 +342,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(&ethtool_genl_family, dev_net(dev), skb,
- 0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
+ struct nlmsghdr *nlh = nlmsg_hdr(skb);
+ int ret;
+
+ mutex_lock(&ethnl_bcast_lock);
+ nlh->nlmsg_seq = ++ethnl_bcast_seq;
+ ret = genlmsg_multicast_netns(&ethtool_genl_family, dev_net(dev), skb,
+ 0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
+ mutex_unlock(&ethnl_bcast_lock);
+ return ret;
}
/* GET request helpers */
@@ -501,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;
@@ -526,13 +536,19 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
goto err_free;
ethnl_init_reply_data(reply_data, ops, req_info->dev);
- rtnl_lock();
- if (req_info->dev)
+ if (req_info->dev) {
+ 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)
+ if (req_info->dev) {
netdev_unlock_ops(req_info->dev);
- rtnl_unlock();
+ if (need_rtnl)
+ rtnl_unlock();
+ }
if (ret < 0)
goto err_dev;
ret = ops->reply_size(req_info, reply_data);
@@ -579,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;
@@ -589,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);
@@ -882,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];
@@ -906,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);
@@ -937,7 +962,8 @@ out_free_cfg:
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:
@@ -1003,7 +1029,7 @@ static void ethnl_default_notify(struct net_device *dev, unsigned int cmd,
ops->req_info_size - sizeof(*req_info));
}
- netdev_ops_assert_locked(dev);
+ netdev_assert_locked_ops_compat(dev);
ethnl_init_reply_data(reply_data, ops, dev);
ret = ops->prepare_data(req_info, reply_data, &info);
@@ -1079,7 +1105,7 @@ void ethnl_notify(struct net_device *dev, unsigned int cmd,
{
if (unlikely(!ethnl_ok))
return;
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
if (likely(cmd < ARRAY_SIZE(ethnl_notify_handlers) &&
ethnl_notify_handlers[cmd]))
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index aaf6f2468768..3e969a070f9f 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,
@@ -116,6 +115,34 @@ static inline void ethnl_update_u8(u8 *dst, const struct nlattr *attr,
}
/**
+ * ethnl_update_u8_u32() - update u8 value from an NLA_U32 attribute
+ * @dst: value to update
+ * @attr: netlink attribute with new value or null
+ * @mod: pointer to bool for modification tracking
+ *
+ * Some attributes are NLA_U32 on the wire but are stored in a u8. Read the
+ * full 32-bit value from NLA_U32 netlink attribute @attr and narrow it into
+ * the u8 pointed to by @dst; do nothing if @attr is null.
+ * Bool pointed to by @mod is set to true if this function changed the value
+ * of *dst, otherwise it is left as is.
+ */
+static inline void ethnl_update_u8_u32(u8 *dst, const struct nlattr *attr,
+ bool *mod)
+{
+ u32 val;
+
+ if (!attr)
+ return;
+ val = nla_get_u32(attr);
+ DEBUG_NET_WARN_ON_ONCE(val > U8_MAX);
+ if (*dst == val)
+ return;
+
+ *dst = val;
+ *mod = true;
+}
+
+/**
* ethnl_update_bool32() - update u32 used as bool from NLA_U8 attribute
* @dst: value to update
* @attr: netlink attribute with new value or null
@@ -276,14 +303,15 @@ static inline void ethnl_parse_header_dev_put(struct ethnl_req_info *req_info)
/**
* ethnl_req_get_phydev() - Gets the phy_device targeted by this request,
- * if any. Must be called under rntl_lock().
+ * if any.
* @req_info: The ethnl request to get the phy from.
* @tb: The netlink attributes array, for error reporting.
* @header: The netlink header index, used for error reporting.
* @extack: The netlink extended ACK, for error reporting.
*
- * The caller must hold RTNL, until it's done interacting with the returned
- * phy_device.
+ * If a phy_device is returned the caller must hold rtnl_lock when calling
+ * this function, and until it's done interacting with the returned phy_device.
+ * IOW caller must hold rtnl_lock unless they know netdev has no phy_device.
*
* Return: A phy_device pointer corresponding either to the passed phy_index
* if one is provided. If not, the phy_device attached to the
@@ -318,12 +346,12 @@ enum ethnl_sock_type {
};
struct ethnl_sock_priv {
- struct net_device *dev;
+ struct net *net;
u32 portid;
enum ethnl_sock_type type;
};
-int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
+int ethnl_sock_priv_set(struct sk_buff *skb, struct net *net, u32 portid,
enum ethnl_sock_type type);
/**
@@ -333,7 +361,9 @@ int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
* @hdr_attr: attribute type for request header
* @req_info_size: size of request info
* @reply_data_size: size of reply data
- * @allow_nodev_do: allow non-dump request with no device identification
+ * @allow_nodev_do:
+ * Allow non-dump request with no device identification.
+ * Note that locks (rtnl_lock etc.) are only taken if device is set.
* @set_ntf_cmd: notification to generate on changes (SET)
* @parse_request:
* Parse request except common header (struct ethnl_req_info). Common
diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c
index d4e6887055ab..018b0412be86 100644
--- a/net/ethtool/phy.c
+++ b/net/ethtool/phy.c
@@ -76,8 +76,8 @@ static int phy_prepare_data(const struct ethnl_req_info *req_info,
struct nlattr **tb = info->attrs;
struct phy_device_node *pdn;
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))
@@ -88,8 +88,19 @@ static int phy_prepare_data(const struct ethnl_req_info *req_info,
return -EOPNOTSUPP;
rep_data->phyindex = phydev->phyindex;
+
rep_data->name = kstrdup(dev_name(&phydev->mdio.dev), GFP_KERNEL);
- rep_data->drvname = kstrdup(phydev->drv->name, GFP_KERNEL);
+ if (!rep_data->name)
+ return -ENOMEM;
+
+ if (phydev->drv) {
+ rep_data->drvname = kstrdup(phydev->drv->name, GFP_KERNEL);
+ if (!rep_data->drvname) {
+ ret = -ENOMEM;
+ goto err_free_name;
+ }
+ }
+
rep_data->upstream_type = pdn->upstream_type;
if (pdn->upstream_type == PHY_UPSTREAM_PHY) {
@@ -97,15 +108,33 @@ static int phy_prepare_data(const struct ethnl_req_info *req_info,
rep_data->upstream_index = upstream->phyindex;
}
- if (pdn->parent_sfp_bus)
+ if (pdn->parent_sfp_bus) {
rep_data->upstream_sfp_name = kstrdup(sfp_get_name(pdn->parent_sfp_bus),
GFP_KERNEL);
+ if (!rep_data->upstream_sfp_name) {
+ ret = -ENOMEM;
+ goto err_free_drvname;
+ }
+ }
- if (phydev->sfp_bus)
+ if (phydev->sfp_bus) {
rep_data->downstream_sfp_name = kstrdup(sfp_get_name(phydev->sfp_bus),
GFP_KERNEL);
+ if (!rep_data->downstream_sfp_name) {
+ ret = -ENOMEM;
+ goto err_free_upstream_sfp;
+ }
+ }
return 0;
+
+err_free_upstream_sfp:
+ kfree(rep_data->upstream_sfp_name);
+err_free_drvname:
+ kfree(rep_data->drvname);
+err_free_name:
+ kfree(rep_data->name);
+ return ret;
}
static int phy_fill_reply(struct sk_buff *skb,
diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c
index 2eb9bdc2dcb9..757c9e0cc856 100644
--- a/net/ethtool/pse-pd.c
+++ b/net/ethtool/pse-pd.c
@@ -62,14 +62,14 @@ static int pse_prepare_data(const struct ethnl_req_info *req_base,
struct phy_device *phydev;
int ret;
- ret = ethnl_ops_begin(dev);
- if (ret < 0)
- return ret;
-
phydev = ethnl_req_get_phydev(req_base, tb, ETHTOOL_A_PSE_HEADER,
info->extack);
if (IS_ERR(phydev))
- return -ENODEV;
+ return PTR_ERR(phydev);
+
+ ret = ethnl_ops_begin(dev);
+ if (ret < 0)
+ return ret;
ret = pse_get_pse_attributes(phydev, info->extack, data);
diff --git a/net/ethtool/rings.c b/net/ethtool/rings.c
index 0fd5dcc3729f..9054c89c5d7b 100644
--- a/net/ethtool/rings.c
+++ b/net/ethtool/rings.c
@@ -63,9 +63,9 @@ static int rings_reply_size(const struct ethnl_req_info *req_base,
nla_total_size(sizeof(u32)) + /* _RINGS_TX */
nla_total_size(sizeof(u32)) + /* _RINGS_RX_BUF_LEN */
nla_total_size(sizeof(u8)) + /* _RINGS_TCP_DATA_SPLIT */
- nla_total_size(sizeof(u32) + /* _RINGS_CQE_SIZE */
+ nla_total_size(sizeof(u32)) + /* _RINGS_CQE_SIZE */
nla_total_size(sizeof(u8)) + /* _RINGS_TX_PUSH */
- nla_total_size(sizeof(u8))) + /* _RINGS_RX_PUSH */
+ nla_total_size(sizeof(u8)) + /* _RINGS_RX_PUSH */
nla_total_size(sizeof(u32)) + /* _RINGS_TX_PUSH_BUF_LEN */
nla_total_size(sizeof(u32)) + /* _RINGS_TX_PUSH_BUF_LEN_MAX */
nla_total_size(sizeof(u32)) + /* _RINGS_HDS_THRESH */
diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c
index 353110b862ab..d4a1a4724b67 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"
@@ -134,8 +135,7 @@ rss_get_data_alloc(struct net_device *dev, struct rss_reply_data *data)
if (!rss_config)
return -ENOMEM;
- if (data->indir_size)
- data->indir_table = (u32 *)rss_config;
+ data->indir_table = (u32 *)rss_config;
if (data->hkey_size)
data->hkey = rss_config + indir_bytes;
@@ -170,8 +170,10 @@ rss_prepare_get(const struct rss_req_info *request, struct net_device *dev,
rxfh.key = data->hkey;
ret = ops->get_rxfh(dev, &rxfh);
- if (ret)
+ if (ret) {
+ rss_get_data_free(data);
goto out_unlock;
+ }
data->hfunc = rxfh.hfunc;
data->input_xfrm = rxfh.input_xfrm;
@@ -467,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;
}
@@ -573,7 +570,7 @@ static const struct nla_policy ethnl_rss_flows_policy[] = {
const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1] = {
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_RSS_CONTEXT] = { .type = NLA_U32, },
- [ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_MIN(NLA_U32, 1),
+ [ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_RANGE(NLA_U32, 1, U8_MAX),
[ETHTOOL_A_RSS_INDIR] = { .type = NLA_BINARY, },
[ETHTOOL_A_RSS_HKEY] = NLA_POLICY_MIN(NLA_BINARY, 1),
[ETHTOOL_A_RSS_INPUT_XFRM] =
@@ -686,7 +683,7 @@ rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
ethtool_rxfh_indir_default(i, num_rx_rings);
}
- *mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
+ *mod |= memcmp(rxfh->indir, data->indir_table, alloc_size);
return user_size;
@@ -854,7 +851,7 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
indir_mod = !!tb[ETHTOOL_A_RSS_INDIR];
rxfh.hfunc = data.hfunc;
- ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
+ ethnl_update_u8_u32(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
if (rxfh.hfunc == data.hfunc)
rxfh.hfunc = ETH_RSS_HASH_NO_CHANGE;
@@ -863,7 +860,8 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
goto exit_free_indir;
rxfh.input_xfrm = data.input_xfrm;
- ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod);
+ ethnl_update_u8_u32(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM],
+ &mod);
xfrm_sym = rxfh.input_xfrm || data.input_xfrm;
if (rxfh.input_xfrm == data.input_xfrm)
rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
@@ -937,7 +935,7 @@ const struct ethnl_request_ops ethnl_rss_request_ops = {
const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1] = {
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_RSS_CONTEXT] = NLA_POLICY_MIN(NLA_U32, 1),
- [ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_MIN(NLA_U32, 1),
+ [ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_RANGE(NLA_U32, 1, U8_MAX),
[ETHTOOL_A_RSS_INDIR] = NLA_POLICY_MIN(NLA_BINARY, 1),
[ETHTOOL_A_RSS_HKEY] = NLA_POLICY_MIN(NLA_BINARY, 1),
[ETHTOOL_A_RSS_INPUT_XFRM] =
@@ -981,19 +979,24 @@ ethnl_rss_create_validate(struct net_device *dev, struct genl_info *info)
}
static void
-ethnl_rss_create_send_ntf(struct sk_buff *rsp, struct net_device *dev)
+ethnl_rss_create_send_ntf(const struct sk_buff *rsp, struct net_device *dev)
{
- struct nlmsghdr *nlh = (void *)rsp->data;
struct genlmsghdr *genl_hdr;
+ struct nlmsghdr *nlh;
+ struct sk_buff *ntf;
+
+ ntf = skb_copy_expand(rsp, 0, 0, GFP_KERNEL);
+ if (!ntf)
+ return;
+ 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;
- ethnl_multicast(rsp, dev);
+ ethnl_multicast(ntf, dev);
}
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
@@ -1031,8 +1034,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)
@@ -1047,14 +1049,15 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
goto exit_clean_data;
indir_user_size = ret;
- ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
+ ethnl_update_u8_u32(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
ret = rss_set_prep_hkey(dev, info, &data, &rxfh, &mod);
if (ret)
goto exit_free_indir;
rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
- ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod);
+ ethnl_update_u8_u32(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM],
+ &mod);
ctx = ethtool_rxfh_ctx_alloc(ops, data.indir_size, data.hkey_size);
if (!ctx) {
@@ -1099,17 +1102,13 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
ntf_fail |= rss_fill_reply(rsp, &req.base, &data.base);
if (WARN_ON(!hdr || ntf_fail)) {
ret = -EMSGSIZE;
- goto exit_unlock;
+ goto err_remove_ctx;
}
genlmsg_end(rsp, hdr);
- /* Use the same skb for the response and the notification,
- * genlmsg_reply() will copy the skb if it has elevated user count.
- */
- skb_get(rsp);
- ret = genlmsg_reply(rsp, info);
ethnl_rss_create_send_ntf(rsp, dev);
+ ret = genlmsg_reply(rsp, info);
rsp = NULL;
exit_unlock:
@@ -1123,14 +1122,17 @@ exit_clean_data:
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:
nlmsg_free(rsp);
return ret;
+err_remove_ctx:
+ if (ops->remove_rxfh_context(dev, ctx, req.rss_context, NULL))
+ /* leave the context on failure, like ethnl_rss_delete_doit() */
+ goto exit_unlock;
err_ctx_id_free:
xa_erase(&dev->ethtool->rss_ctx, req.rss_context);
err_unlock_free_ctx:
@@ -1168,11 +1170,12 @@ int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info)
dev = req.dev;
ops = dev->ethtool_ops;
- if (!ops->create_rxfh_context)
+ if (!ops->create_rxfh_context) {
+ ret = -EOPNOTSUPP;
goto exit_free_dev;
+ }
- rtnl_lock();
- netdev_lock_ops(dev);
+ netdev_lock_ops_compat(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
@@ -1202,8 +1205,7 @@ exit_unlock:
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;
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
index bb1e829ba099..94c4718d31ae 100644
--- a/net/ethtool/strset.c
+++ b/net/ethtool/strset.c
@@ -311,7 +311,7 @@ static int strset_prepare_data(const struct ethnl_req_info *req_base,
return 0;
}
- phydev = ethnl_req_get_phydev(req_base, tb, ETHTOOL_A_HEADER_FLAGS,
+ phydev = ethnl_req_get_phydev(req_base, tb, ETHTOOL_A_STRSET_HEADER,
info->extack);
/* phydev can be NULL, check for errors only */
diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c
index e4f518e49d4c..6be3aa5d4bc1 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;
@@ -69,8 +70,10 @@ static int tsconfig_prepare_data(const struct ethnl_req_info *req_base,
if (ret)
goto out;
- if (ts_info.phc_index == -1)
- return -ENODEV;
+ if (ts_info.phc_index == -1) {
+ ret = -ENODEV;
+ goto out;
+ }
data->hwprov_desc.index = ts_info.phc_index;
data->hwprov_desc.qualifier = ts_info.phc_qualifier;
@@ -211,7 +214,7 @@ static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info)
return -ENOMEM;
}
- ASSERT_RTNL();
+ netdev_assert_locked_ops_compat(dev);
reply_data->base.dev = dev;
ret = tsconfig_prepare_data(&req_info->base, &reply_data->base, info);
if (ret < 0)
@@ -224,16 +227,21 @@ static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info)
reply_len = ret + ethnl_reply_header_size();
rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_MSG_TSCONFIG_SET_REPLY,
ETHTOOL_A_TSCONFIG_HEADER, info, &reply_payload);
- if (!rskb)
+ if (!rskb) {
+ ret = -ENOMEM;
goto err_cleanup;
+ }
ret = tsconfig_fill_reply(rskb, &req_info->base, &reply_data->base);
if (ret < 0)
- goto err_cleanup;
+ goto err_free_msg;
genlmsg_end(rskb, reply_payload);
ret = genlmsg_reply(rskb, info);
+ rskb = NULL;
+err_free_msg:
+ nlmsg_free(rskb);
err_cleanup:
kfree(reply_data);
kfree(req_info);
@@ -302,10 +310,6 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base,
struct nlattr **tb = info->attrs;
int ret;
- BUILD_BUG_ON(__HWTSTAMP_TX_CNT >= 32);
- BUILD_BUG_ON(__HWTSTAMP_FILTER_CNT >= 32);
- BUILD_BUG_ON(__HWTSTAMP_FLAG_CNT > 32);
-
if (!netif_device_present(dev))
return -ENODEV;
@@ -313,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;
@@ -355,8 +359,10 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base,
if (ret < 0)
goto err_free_hwprov;
- /* Select only one tx type at a time */
- if (ffs(req_tx_type) != fls(req_tx_type)) {
+ /* Select exactly one tx type at a time */
+ if (hweight32(req_tx_type) != 1) {
+ NL_SET_BAD_ATTR(info->extack,
+ tb[ETHTOOL_A_TSCONFIG_TX_TYPES]);
ret = -EINVAL;
goto err_free_hwprov;
}
@@ -376,8 +382,10 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base,
if (ret < 0)
goto err_free_hwprov;
- /* Select only one rx filter at a time */
- if (ffs(req_rx_filter) != fls(req_rx_filter)) {
+ /* Select exactly one rx filter at a time */
+ if (hweight32(req_rx_filter) != 1) {
+ NL_SET_BAD_ATTR(info->extack,
+ tb[ETHTOOL_A_TSCONFIG_RX_FILTERS]);
ret = -EINVAL;
goto err_free_hwprov;
}
@@ -411,7 +419,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_is_locked_ops_compat(dev));
if (__hwprov)
kfree_rcu(__hwprov, rcu_head);
}
diff --git a/net/ethtool/tsinfo.c b/net/ethtool/tsinfo.c
index a865f0fdd26b..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"
@@ -83,6 +84,11 @@ tsinfo_parse_request(struct ethnl_req_info *req_base,
if (!tb[ETHTOOL_A_TSINFO_HWTSTAMP_PROVIDER])
return 0;
+ if (req_base->flags & ETHTOOL_FLAG_STATS) {
+ NL_SET_ERR_MSG(extack, "can't query statistics for a provider");
+ return -EOPNOTSUPP;
+ }
+
return ts_parse_hwtst_provider(tb[ETHTOOL_A_TSINFO_HWTSTAMP_PROVIDER],
&req->hwprov_desc, extack, &mod);
}
@@ -402,10 +408,8 @@ static int ethnl_tsinfo_dump_one_netdev(struct sk_buff *skb,
continue;
ehdr = ethnl_tsinfo_prepare_dump(skb, dev, reply_data, cb);
- if (IS_ERR(ehdr)) {
- ret = PTR_ERR(ehdr);
- goto err;
- }
+ if (IS_ERR(ehdr))
+ return PTR_ERR(ehdr);
reply_data->ts_info.phc_qualifier = ctx->pos_phcqualifier;
ret = ops->get_ts_info(dev, &reply_data->ts_info);
@@ -470,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;
}
@@ -523,6 +524,12 @@ int ethnl_tsinfo_start(struct netlink_callback *cb)
if (ret < 0)
goto free_reply_data;
+ if (req_info->base.flags & ETHTOOL_FLAG_STATS) {
+ NL_SET_ERR_MSG(cb->extack, "stats not supported in dump");
+ ret = -EOPNOTSUPP;
+ goto err_dev_put;
+ }
+
ctx->req_info = req_info;
ctx->reply_data = reply_data;
ctx->pos_ifindex = 0;
@@ -532,6 +539,8 @@ int ethnl_tsinfo_start(struct netlink_callback *cb)
return 0;
+err_dev_put:
+ ethnl_parse_header_dev_put(&req_info->base);
free_reply_data:
kfree(reply_data);
free_req_info: