diff options
Diffstat (limited to 'net/ethtool')
| -rw-r--r-- | net/ethtool/bitset.c | 8 | ||||
| -rw-r--r-- | net/ethtool/cabletest.c | 12 | ||||
| -rw-r--r-- | net/ethtool/cmis.h | 4 | ||||
| -rw-r--r-- | net/ethtool/cmis_cdb.c | 12 | ||||
| -rw-r--r-- | net/ethtool/cmis_fw_update.c | 52 | ||||
| -rw-r--r-- | net/ethtool/coalesce.c | 6 | ||||
| -rw-r--r-- | net/ethtool/common.c | 4 | ||||
| -rw-r--r-- | net/ethtool/common.h | 87 | ||||
| -rw-r--r-- | net/ethtool/eeprom.c | 10 | ||||
| -rw-r--r-- | net/ethtool/ioctl.c | 151 | ||||
| -rw-r--r-- | net/ethtool/linkinfo.c | 4 | ||||
| -rw-r--r-- | net/ethtool/linkmodes.c | 4 | ||||
| -rw-r--r-- | net/ethtool/linkstate.c | 6 | ||||
| -rw-r--r-- | net/ethtool/mm.c | 5 | ||||
| -rw-r--r-- | net/ethtool/module.c | 49 | ||||
| -rw-r--r-- | net/ethtool/netlink.c | 76 | ||||
| -rw-r--r-- | net/ethtool/netlink.h | 44 | ||||
| -rw-r--r-- | net/ethtool/phy.c | 37 | ||||
| -rw-r--r-- | net/ethtool/pse-pd.c | 10 | ||||
| -rw-r--r-- | net/ethtool/rings.c | 4 | ||||
| -rw-r--r-- | net/ethtool/rss.c | 72 | ||||
| -rw-r--r-- | net/ethtool/strset.c | 2 | ||||
| -rw-r--r-- | net/ethtool/tsconfig.c | 41 | ||||
| -rw-r--r-- | net/ethtool/tsinfo.c | 51 |
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, ð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 +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(ð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 */ @@ -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: |
